ASP Core Tutorial
ASP Core Tutorial
ASP Core Tutorial
Introducción
Novedades
Novedades
Novedades
Introducción
Creación de una aplicación web
Creación de una API Web
Tutoriales
Creación de una aplicación web de páginas de Razor
Introducción a las páginas de Razor
Agregar un modelo
Páginas de Razor con scaffolding
SQL Server LocalDB
Actualización de las páginas
Agregar búsqueda
Agregar un campo nuevo
Agregar validación
Carga de archivos
Creación de una aplicación web MVC
Introducción
Agregar un controlador
Agregar una vista
Agregar un modelo
Trabajar con SQL Server LocalDB
Vistas y métodos de controlador
Agregar búsqueda
Agregar un campo nuevo
Agregar validación
Examinar los métodos Details y Delete
Compilación de API web
Crear una Web API en Visual Studio Code
Crear una Web API en Visual Studio para Mac
Crear una Web API en Visual Studio para Windows
Creación de servicios back-end para aplicaciones móviles nativas
Páginas de ayuda mediante Swagger
Get started with NSwag
Get started with Swashbuckle
Acceso a datos con EF Core
Acceso a datos con páginas de Razor y EF Core
Introducción
Operaciones de creación, lectura, actualización y eliminación
Ordenación, filtrado, paginación y agrupación
Migraciones
Creación de un modelo de datos complejo
Lectura de datos relacionados
Actualización de datos relacionados
Control de conflictos de simultaneidad
Acceso a datos: MVC con EF Core
Introducción
Operaciones de creación, lectura, actualización y eliminación
Ordenación, filtrado, paginación y agrupación
Migraciones
Creación de un modelo de datos complejo
Lectura de datos relacionados
Actualización de datos relacionados
Control de conflictos de simultaneidad
Herencia
Temas avanzados
Tutoriales multiplataforma
Aplicación web de páginas de Razor en macOS
Introducción a las páginas de Razor
Agregar un modelo
Páginas de Razor con scaffolding
Trabajar con SQLite
Actualización de las páginas
Agregar búsqueda
Aplicación web de páginas de Razor con VS Code
Introducción a las páginas de Razor
Agregar un modelo
Páginas de Razor con scaffolding
Trabajar con SQLite
Actualización de las páginas
Agregar búsqueda
Aplicación web MVC con Visual Studio para Mac
Introducción
Agregar un controlador
Agregar una vista
Agregar un modelo
Trabajar con SQLite
Vistas y métodos de controlador
Agregar búsqueda
Agregar un campo nuevo
Agregar validación
Examinar los métodos Details y Delete
Aplicación web MWC con Visual Studio Code en macOS o Linux
Introducción
Agregar un controlador
Agregar una vista
Agregar un modelo
Trabajar con SQLite
Vistas y métodos de controlador
Agregar búsqueda
Agregar un campo nuevo
Agregar validación
Examinar los métodos Details y Delete
API web con Visual Studio para Mac
API web con Visual Studio Code
Desarrollo de aplicaciones mediante un monitor de archivos
Creación de servicios back-end para aplicaciones móviles
Aspectos básicos
Inicio de aplicaciones
Inserción de dependencias (servicios)
Middleware
Middleware
Middleware basado en Factory
Middleware basado en Factory con un contenedor de terceros
Archivos estáticos
Enrutamiento
Middleware de reescritura de dirección URL
Uso de varios entornos
Configuración y opciones
Configuración
Opciones
Mejora de una aplicación a partir de un ensamblado externo
Registro
Registro con LoggerMessage
Control de errores
Proveedores de archivos
Host
Host web
Host genérico
Tareas en segundo plano con servicios hospedados
Estado de sesión y aplicación
Servidores
Kestrel
Módulo ASP.NET Core
HTTP.sys
Globalización y localización
Configuración de la localización de un objeto portable con Orchard Core
Inicio de solicitudes HTTP
Características de la solicitud
Elementos primitivos
Cambio de tokens
Apertura de la interfaz web para .NET (OWIN)
WebSockets
Metapaquete Microsoft.AspNetCore.App
Metapaquete Microsoft.AspNetCore.All
Elección entre .NET Core y .NET Framework
Elección entre ASP.NET y ASP.NET Core
Páginas de Razor
Métodos de filtrado para páginas de Razor
Creación de una biblioteca de clases de Razor
Convenciones de rutas y aplicaciones
SDK de Razor
MVC
Enlace de modelos
Validación de modelos
Vistas
Sintaxis de Razor
Visualización de compilación
Diseño
Aplicaciones auxiliares de etiquetas
Creación de aplicaciones auxiliares de etiquetas
Uso de aplicaciones auxiliares de etiquetas en formularios
Aplicaciones auxiliares de etiquetas integradas
Aplicación auxiliar de etiquetas de delimitador
Aplicación auxiliar de etiquetas de caché
Aplicación auxiliar de etiquetas de caché distribuida
Aplicación auxiliar de etiquetas de entorno
Aplicación auxiliar de etiquetas de formulario
Aplicación auxiliar de etiquetas de imagen
Aplicación auxiliar de etiquetas de entrada
Aplicación auxiliar de etiquetas de elementos de etiqueta
Aplicación auxiliar de etiquetas parciales
Aplicación auxiliar de etiquetas de selección
Aplicación auxiliar de etiquetas de área de texto
Aplicación auxiliar de etiquetas de mensaje de validación
Aplicación auxiliar de etiquetas de resumen de validación
Vistas parciales
Inserción de dependencias en vistas
Visualización de componentes
Controladores
Enrutamiento a acciones del controlador
Cargas de archivos
Inserción de dependencias en controladores
Controladores de pruebas
Avanzadas
Trabajar con el modelo de aplicación
Filtros
Áreas
Elementos de la aplicación
Enlace de modelos personalizado
API Web
Tipos de valor devuelto de acción del controlador
Avanzadas
Formateadores personalizados
Aplicación de formato a datos de respuesta
Probar, depurar y solucionar problemas
Pruebas unitarias
Pruebas de integración
Pruebas unitarias de páginas de Razor
Controladores de pruebas
Depuración remota
Depuración de instantáneas
Depuración en Visual Studio
Solucionar problemas
Acceso a datos con EF Core y Azure
Introducción a las páginas de Razor y EF Core con Visual Studio
Introducción a ASP.NET Core y EF Core con Visual Studio
ASP.NET Core con EF Core: nueva base de datos
ASP.NET Core con EF Core: base de datos existente
Introducción a ASP.NET Core y Entity Framework 6
Azure Storage
Agregar Azure Storage mediante el uso de Servicios conectados de Visual Studio
Introducción a Blob Storage y Servicios conectados de Visual Studio
Introducción a Queue Storage y Servicios conectados de Visual Studio
Introducción a Table Storage y Servicios conectados de Visual Studio
Desarrollo del lado cliente
Uso de Gulp
Uso de Grunt
Administración de paquetes de cliente con Bower
Creación de sitios con capacidad de respuesta con Bootstrap
Aplicación de estilo a aplicaciones con LESS, Sass y Font Awesome
Agrupar y minimizar
Uso de Vínculo con exploradores
Uso de JavaScriptServices para aplicaciones de página única
Uso de plantillas de proyectos de aplicaciones de página única
Plantilla de proyecto Angular
Plantilla de proyecto React
Plantilla de proyecto React con Redux
SignalR
Introducción
Introducción
Introducción
Concentradores
Cliente de JavaScript
Cliente .NET
HubContext
Usuarios y grupos
Protocolo de concentrador MessagePack
Publicar en Azure
Streaming
Plataformas compatibles
Móvil
Creación de servicios back-end para aplicaciones móviles nativas
Hospedaje e implementación
Hospedaje en Azure App Service
Publicación en Azure con Visual Studio
Publicación en Azure con herramientas de CLI
Implementación continua en Azure con Visual Studio y Git
Implementación continua en Azure con VSTS
Solución de problemas de ASP.NET Core en Azure App Service
Hospedaje en Windows con IIS
Solución de problemas de ASP.NET Core en IIS
Referencia de configuración del módulo ASP.NET Core
Compatibilidad de IIS de tiempo de desarrollo en Visual Studio para ASP.NET Core
Módulos de IIS con ASP.NET Core
Hospedaje en un servicio de Windows
Hospedaje en Linux con Nginx
Hospedaje en Linux con Apache
Hospedaje en Docker
Creación de imágenes de Docker
Visual Studio Tools para Docker
Publicación en una imagen de Docker
Configuración del proxy y del equilibrador de carga
Perfiles de publicación de Visual Studio
Estructura de directorios
Referencia de errores comunes de Azure App Service e IIS
Seguridad
Autenticación
Introducción a Identity
Identidad de scaffolding
Agregar datos de usuario personalizados a Identity
Opciones de autenticación de OSS de la comunidad
Configuración de Identity
Configuración de la autenticación de Windows
Configuración del tipo de clave principal para Identity
Proveedores de almacenamiento personalizados para la identidad
Habilitar la autenticación con Facebook, Google y otros proveedores externos
Autenticación con Facebook
Autenticación con Twitter
Autenticación con Google
Autenticación con Microsoft
Otros proveedores de autenticación
Autenticación con WS-Federation
Confirmación de cuentas y recuperación de contraseñas
Habilitar la generación de código QR en Identity
Autenticación en dos fases con SMS
Uso de la autenticación de cookies sin identidad
Azure Active Directory
Integración de Azure AD en una aplicación web de ASP.NET Core
Integración de Azure AD B2C en una aplicación web de ASP.NET Core dirigida a
los clientes
Integración de Azure AD B2C en una API web de ASP.NET Core
Llamada a una API web de ASP.NET Core desde una aplicación de WPF con Azure
AD
Llamada a una API web en una aplicación web de ASP.NET Core con Azure AD
Protección de aplicaciones de ASP.NET Core con IdentityServer4
Protección de aplicaciones de ASP.NET Core con la autenticación de Azure App
Service (autenticación sencilla)
Cuentas de usuario individuales
Autorización
Introducción
Creación de una aplicación con datos de usuario protegidos por autorización
Autorización de páginas de Razor
Autorización simple
Autorización basada en roles
Autorización basada en notificaciones
Autorización basada en directivas
Inserción de dependencias en controladores de requisitos
Autorización basada en recursos
Autorización basada en visualizaciones
Limitación de la identidad por esquema
Protección de datos
Introducción a la protección de datos
Introducción a las API de protección de datos
API de consumidor
Información general sobre las API de consumidor
Cadenas de propósito
Jerarquía de propósito y configuración multiempresa
Aplicar un algoritmo hash a las contraseñas
Limitación de la duración de cargas protegidas
Desprotección de cargas cuyas claves se han revocado
Configuración
Configuración de la protección de datos
Configuración predeterminada
Directiva de todo el equipo
Escenarios no compatibles con DI
API de extensibilidad
Extensibilidad de criptografía de núcleo
Extensibilidad de administración de claves
Otras API
Implementación
Detalles de cifrado autenticado
Derivación de subclave y cifrado autenticado
Encabezados de contexto
Administración de claves
Proveedores de almacenamiento de claves
Cifrado de claves en reposo
Inmutabilidad de claves y configuración
Formato de almacenamiento de claves
Proveedores de protección de datos efímeros
Compatibilidad
Reemplazar <machineKey> en ASP.NET
Aplicación de HTTPS
Compatibilidad con el Reglamento general de protección de datos (GDPR) de la UE
Almacenamiento seguro de secretos de aplicación en el desarrollo
Proveedor de configuración de Azure Key Vault
Prevención de ataques de falsificación de solicitudes
Prevención de ataques de redireccionamiento abierto
Prevención de scripting entre sitios
Habilitar solicitudes entre orígenes (CORS)
Compartir cookies entre aplicaciones
Rendimiento
Almacenamiento en caché de respuestas
Almacenamiento en caché en memoria
Trabajar con una memoria caché distribuida
Almacenamiento en caché de respuestas
Middleware de almacenamiento en caché de respuestas
Middleware de compresión de respuestas
Migración
ASP.NET Core 2.0 a 2.1
De ASP.NET a ASP.NET Core
MVC
API Web
Configuración
Autenticación e identidad
ClaimsPrincipal.Current
De pertenencia a identidad
Módulos HTTP a middleware
De ASP.NET Core 1.x a 2.0
Autenticación e identidad
Referencia de API
Colaboracion
Introducción a ASP.NET Core
21/06/2018 • 6 minutes to read • Edit Online
Pasos siguientes
Para obtener más información, vea los siguientes recursos:
Introducción a las páginas de Razor
Tutoriales de ASP.NET Core
Conceptos básicos de ASP.NET Core
La reunión semanal de la comunidad de ASP.NET trata el progreso y los planes del equipo. Incluye nuevos
blogs y nuevo software de terceros.
2 minutes to read
Crear una API web con ASP.NET Core y Visual Studio
para Windows
21/06/2018 • 25 minutes to read • Edit Online
Información general
En este tutorial se crea la siguiente API:
GET /api/todo Obtener todas las tareas Ninguna Matriz de tareas pendientes
pendientes
El cliente es todo lo que consume la API web (aplicación móvil, explorador, etcétera). En este tutorial no se
crea ningún cliente. Postman o curl se utilizan como el cliente para probar la aplicación.
Un modelo es un objeto que representa los datos de la aplicación. En este caso, el único modelo es una tarea
pendiente. Los modelos se representan como clases de C#, también conocidas como clases POCO (del
inglés Plain Old C# Object, objetos CRL antiguos sin formato).
Un controlador es un objeto que controla solicitudes HTTP y crea la respuesta HTTP. Esta aplicación tiene un
único controlador.
Para simplificar el tutorial, la aplicación no usa una base de datos persistente. La aplicación de ejemplo
almacena tareas pendientes en una base de datos en memoria.
Requisitos previos
Visual Studio for Windows.
Select the ASP.NET and web development workload.
.Net Core 2.1 SDK
Crear el proyecto
Haga lo siguiente para descargar Visual Studio:
En el menú Archivo, seleccione Nuevo > Proyecto.
Seleccione la plantilla Aplicación web ASP.NET Core. Denomine el proyecto TodoApi y haga clic en Aceptar.
En el cuadro de diálogo Nueva aplicación web ASP.NET Core - TodoApi, seleccione la versión ASP.NET
Core. Seleccione la plantilla API y haga clic en Aceptar. No seleccione Habilitar compatibilidad con Docker.
Iniciar la aplicación
En Visual Studio, presione CTRL+F5 para iniciar la aplicación. Visual Studio inicia un explorador y navega hasta
http://localhost:<port>/api/values , donde <port> es un número de puerto elegido aleatoriamente. En Chrome,
Microsoft Edge y Firefox se muestra la salida siguiente:
["value1","value2"]
NOTE
Las clases del modelo pueden ir en cualquier parte del proyecto. La carpeta Models se usa por convención para las clases de
modelos.
En el Explorador de soluciones, haga clic con el botón derecho en la carpeta de modelos y seleccione Agregar >
Clase. Denomine la clase TodoItem y, después, haga clic en Agregar.
Actualice la clase TodoItem por el siguiente código:
namespace TodoApi.Models
{
public class TodoItem
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
}
}
using Microsoft.EntityFrameworkCore;
namespace TodoApi.Models
{
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}
Tareas pendientes
Para obtener tareas pendientes, agregue estos métodos a la clase TodoController :
[!code-csharp]
[!code-csharp]
Estos métodos implementan los dos métodos GET:
GET /api/todo
GET /api/todo/{id}
Más adelante en el tutorial, veremos cómo se puede ver la respuesta HTTP por medio de Postman o curl.
Enrutamiento y rutas URL
El atributo [HttpGet] indica un método que responde a una solicitud HTTP GET. La ruta de dirección URL para
cada método se construye como sigue:
Tome la cadena de plantilla en el atributo Route del controlador:
[!code-csharp]
[!code-csharp]
Reemplace [controller] por el nombre del controlador, que es el nombre de clase de controlador sin el sufijo
"Controller". En este ejemplo, el nombre de clase de controlador es TodoController y el nombre de raíz es
"todo". El enrutamiento en ASP.NET Core no distingue entre mayúsculas y minúsculas.
Si el atributo [HttpGet] tiene una plantilla de ruta (como [HttpGet("/products")] ), anexiónela a la ruta de
acceso. En este ejemplo no se usa una plantilla. Para más información, vea Enrutamiento mediante atributos con
atributos Http[Verb].
En el siguiente método GetById , "{id}" es una variable de marcador de posición correspondiente al identificador
único de la tarea pendiente. Cuando GetById se invoca, asigna el valor "{id}" de la dirección URL al parámetro
id del método.
[!code-csharp]
[!code-csharp]
Name = "GetTodo" crea una ruta con nombre. Rutas con nombre:
Permita que la aplicación cree un vínculo HTTP mediante el nombre de ruta.
Se explican más adelante en el tutorial.
Valores devueltos
El método GetAll devuelve una colección de objetos TodoItem . MVC serializa automáticamente el objeto a JSON
y escribe el JSON en el cuerpo del mensaje de respuesta. El código de respuesta para este método es 200,
suponiendo que no haya ninguna excepción no controlada. Las excepciones no controladas se convierten en
errores 5xx.
En cambio, el método GetById devuelve el tipo más general IActionResult, que representa una amplia gama de
tipos de valor devuelto. GetById tiene dos tipos de valor devueltos distintos:
Si no hay ningún elemento que coincida con el identificador solicitado, el método devuelve un error 404.
Devolver NotFound genera una respuesta HTTP 404.
En caso contrario, el método devuelve 200 con un cuerpo de respuesta JSON. Devolver Ok genera una
respuesta HTTP 200.
En cambio, el método GetById devuelve el tipo ActionResult<T>, que representa una amplia gama de tipos de
valor devuelto. GetById tiene dos tipos de valor devueltos distintos:
Si no hay ningún elemento que coincida con el identificador solicitado, el método devuelve un error 404.
Devolver NotFound genera una respuesta HTTP 404.
En caso contrario, el método devuelve 200 con un cuerpo de respuesta JSON. Devolver item genera una
respuesta HTTP 200.
Iniciar la aplicación
En Visual Studio, presione CTRL+F5 para iniciar la aplicación. Visual Studio inicia un explorador y navega hasta
http://localhost:<port>/api/values , donde <port> es un número de puerto elegido aleatoriamente. Vaya al
controlador Todo en http://localhost:<port>/api/todo .
[!code-csharp]
[!code-csharp]
Usar Postman para enviar una solicitud de creación
Inicia la aplicación.
Abra Postman.
Actualice el número de puerto en la dirección URL de localhost.
Establezca el método HTTP en POST.
Haga clic en la pestaña Body (Cuerpo).
Seleccione el botón de radio Raw (Sin formato).
Establezca el tipo en JSON (application/json).
Escriba un cuerpo de solicitud con una tarea pendiente parecida al siguiente JSON:
{
"name":"walk dog",
"isComplete":true
}
TIP
Si no aparece ninguna respuesta tras hacer clic en Send (Enviar), deshabilite la opción SSL certification verification
(Comprobación de certificación SSL). La encontrará en File > Settings (Archivo > Configuración). Vuelva a hacer clic en el
botón Send (Enviar) después de deshabilitar la configuración.
Haga clic en la pestaña Headers (Encabezados) del panel Response (Respuesta) y copie el valor de encabezado de
Location (Ubicación):
El URI del encabezado de ubicación puede utilizarse para acceder al nuevo elemento.
Actualizar
Agregue el siguiente método Update :
[!code-csharp]
[!code-csharp]
Update es similar a Create , salvo por el hecho de que usa HTTP PUT. La respuesta es 204 Sin contenido. Según la
especificación HTTP, una solicitud PUT requiere que el cliente envíe toda la entidad actualizada, no solo los deltas.
Para admitir actualizaciones parciales, use HTTP PATCH.
Use Postman para actualizar el nombre de la tarea pendiente a "walk cat":
Eliminar
Agregue el siguiente método Delete :
[HttpDelete("{id}")]
public IActionResult Delete(long id)
{
var todo = _context.TodoItems.Find(id);
if (todo == null)
{
return NotFound();
}
_context.TodoItems.Remove(todo);
_context.SaveChanges();
return NoContent();
}
Agregue un archivo HTML denominado index.html al directorio wwwroot del proyecto. Reemplace el contenido
por el siguiente marcado:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>To-do CRUD</title>
<style>
input[type='submit'], button, [aria-label] {
cursor: pointer;
}
}
#spoiler {
display: none;
}
table {
font-family: Arial, sans-serif;
border: 1px solid;
border-collapse: collapse;
}
th {
background-color: #0066CC;
color: white;
}
td {
border: 1px solid;
padding: 5px;
}
</style>
</head>
<body>
<h1>To-do CRUD</h1>
<h3>Add</h3>
<form action="javascript:void(0);" method="POST" onsubmit="addItem()">
<input type="text" id="add-name" placeholder="New to-do">
<input type="submit" value="Add">
</form>
<div id="spoiler">
<h3>Edit</h3>
<form class="my-form">
<input type="hidden" id="edit-id">
<input type="checkbox" id="edit-isComplete">
<input type="text" id="edit-name">
<input type="submit" value="Edit">
<a onclick="closeInput()" aria-label="Close">✖</a>
</form>
</div>
<p id="counter"></p>
<table>
<tr>
<th>Is Complete</th>
<th>Name</th>
<th></th>
<th></th>
</tr>
<tbody id="todos"></tbody>
</table>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script src="site.js"></script>
</body>
</html>
Agregue un archivo JavaScript denominado site.js al directorio wwwroot del proyecto. Reemplace el contenido por
el siguiente código:
$(document).ready(function () {
getData();
});
function getData() {
$.ajax({
type: 'GET',
url: uri,
success: function (data) {
$('#todos').empty();
getCount(data.length);
$.each(data, function (key, item) {
const checked = item.isComplete ? 'checked' : '';
todos = data;
}
});
}
function addItem() {
const item = {
'name': $('#add-name').val(),
'isComplete': false
};
$.ajax({
type: 'POST',
accepts: 'application/json',
url: uri,
contentType: 'application/json',
data: JSON.stringify(item),
error: function (jqXHR, textStatus, errorThrown) {
alert('here');
},
success: function (result) {
getData();
$('#add-name').val('');
}
});
}
function deleteItem(id) {
$.ajax({
url: uri + '/' + id,
type: 'DELETE',
success: function (result) {
getData();
}
});
}
function editItem(id) {
$.each(todos, function (key, item) {
if (item.id === id) {
$('#edit-name').val(item.name);
$('#edit-id').val(item.id);
$('#edit-isComplete').val(item.isComplete);
}
});
$('#spoiler').css({ 'display': 'block' });
}
$('.my-form').on('submit', function () {
const item = {
'name': $('#edit-name').val(),
'isComplete': $('#edit-isComplete').is(':checked'),
'id': $('#edit-id').val()
};
$.ajax({
url: uri + '/' + $('#edit-id').val(),
type: 'PUT',
accepts: 'application/json',
contentType: 'application/json',
data: JSON.stringify(item),
success: function (result) {
getData();
}
});
closeInput();
return false;
});
function closeInput() {
$('#spoiler').css({ 'display': 'none' });
}
Puede que sea necesario realizar un cambio en la configuración de inicio del proyecto de ASP.NET Core para
comprobar la página HTML localmente. Abra launchSettings.json en el directorio Properties del proyecto. Quite la
propiedad launchUrl para forzar a la aplicación a abrirse en index.html, esto es, el archivo predeterminado del
proyecto.
Existen varias formas de obtener jQuery. En el fragmento de código anterior, la biblioteca se carga desde una red
CDN. Este ejemplo es un ejemplo de CRUD completo de llamada a la API de jQuery. Existen más características en
este ejemplo para hacer que la experiencia sea más completa. Aquí verá algunas explicaciones sobre las llamadas a
la API.
Obtener una lista de tareas pendientes
Para obtener una lista de tareas pendientes, envíe una solicitud HTTP GET a /api/todo.
La función de JQuery ajax envía una solicitud AJAX a la API, que devuelve código JSON que representa un objeto
o una matriz. Esta función puede controlar todas las formas de interacción de HTTP enviando una solicitud HTTP a
la url especificada. GET se emite como type . La función de devolución de llamada success se invoca si la
solicitud se realiza correctamente. En la devolución de llamada, el DOM se actualiza con la información de la tarea
pendiente.
$(document).ready(function () {
getData();
});
function getData() {
$.ajax({
type: 'GET',
url: uri,
success: function (data) {
$('#todos').empty();
getCount(data.length);
$.each(data, function (key, item) {
const checked = item.isComplete ? 'checked' : '';
todos = data;
}
});
}
function addItem() {
const item = {
'name': $('#add-name').val(),
'isComplete': false
};
$.ajax({
type: 'POST',
accepts: 'application/json',
url: uri,
contentType: 'application/json',
data: JSON.stringify(item),
error: function (jqXHR, textStatus, errorThrown) {
alert('here');
},
success: function (result) {
getData();
$('#add-name').val('');
}
});
}
$.ajax({
url: uri + '/' + $('#edit-id').val(),
type: 'PUT',
accepts: 'application/json',
contentType: 'application/json',
data: JSON.stringify(item),
success: function (result) {
getData();
}
});
$.ajax({
url: uri + '/' + id,
type: 'DELETE',
success: function (result) {
getData();
}
});
Pasos siguientes
Para más información sobre cómo usar una base de datos persistente, vea:
Creación de una aplicación web de páginas de Razor con ASP.NET Core
Trabajo con datos en ASP.NET Core
Páginas de ayuda de ASP.NET Core Web API mediante Swagger
Enrutamiento a acciones del controlador
Compilación de API web con ASP.NET Core
Tipos de valor devuelto de acción del controlador
Para obtener información sobre la implementación de una API, como en Azure App Service, vea la
documentación sobre Hospedaje e implementación.
Vea o descargue el código de ejemplo. Vea cómo descargarlo.
Tutoriales de ASP.NET Core
12/06/2018 • 3 minutes to read • Edit Online
Están disponibles las siguientes guías detalladas para desarrollar aplicaciones de ASP.NET Core:
Autenticación y autorización
Habilitar la autenticación con Facebook, Google y otros proveedores externos
Confirmación de cuentas y recuperación de contraseñas
Autenticación en dos fases con SMS
Prueba
Pruebas unitarias de .NET Core mediante dotnet test
Hospedaje e implementación
Implementar una aplicación web de ASP.NET Core en Azure con Visual Studio
Implementar una aplicación web de ASP.NET Core en Azure con la línea de comandos
Publicación en una aplicación web de Azure con una implementación continua
Implementar un contenedor de ASP.NET en un host remoto de Docker
ASP.NET Core y Azure Service Fabric
En esta serie se explican los conceptos básicos de la creación de una aplicación web de páginas de Razor con
ASP.NET Core mediante Visual Studio. Otras versiones de esta serie incluyen una versión para macOS y una
versión de Visual Studio Code.
1. Introducción a las páginas de Razor
2. Adición de un modelo a una aplicación de páginas de Razor
3. Páginas de Razor con scaffolding
4. Trabajar con SQL Server LocalDB
5. Actualización de páginas
6. Agregar búsqueda
7. Agregar un campo nuevo
8. Agregar validación
9. Carga de archivos
Introducción a las páginas de Razor en ASP.NET
Core
17/05/2018 • 6 minutes to read • Edit Online
Requisitos previos
Visual Studio for Windows.
Select the ASP.NET and web development workload.
.Net Core 2.1 SDK
NOTE
Para usar ASP.NET Core con .NET Framework, primero debe seleccionar .NET Framework en la lista desplegable
situada en el cuadro de diálogo de la izquierda y luego puede seleccionar la versión de ASP.NET Core deseada.
La plantilla de Visual Studio crea un proyecto de inicio:
Presione F5 para ejecutar la aplicación en modo de depuración o Ctrl-F5 para que se ejecute sin adjuntar el
depurador.
Visual Studio inicia IIS Express y ejecuta la aplicación. En la barra de direcciones aparece localhost:port#
(y no algo como example.com ). Esto es así porque localhost es el nombre de host estándar del equipo
local. Localhost solo sirve las solicitudes web del equipo local. Cuando Visual Studio crea un proyecto web,
se usa un puerto aleatorio para el servidor web. En la imagen anterior, el número de puerto es 5000. Al
ejecutar la aplicación verá otro puerto distinto.
Iniciar la aplicación con CTRL+F5 (modo de no depuración) le permite efectuar cambios en el código,
guardar el archivo, actualizar el explorador y ver los cambios de código. Muchos desarrolladores prefieren
usar el modo de no depuración para iniciar la aplicación rápidamente y ver los cambios.
La plantilla predeterminada crea los vínculos y las páginas RazorPagesMovie, Inicio, Sobre y Contacto.
Según el tamaño de la ventana del explorador, tendrá que hacer clic en el icono de navegación para mostrar
los vínculos.
Pruebe los vínculos. Los vínculos RazorPagesMovie e Inicio dirigen a la página de índice. Los vínculos
Acerca de y Contacto dirigen a las páginas About y Contact respectivamente.
appsettings.json Configuración
Carpeta Páginas
El archivo _Layout.cshtml contiene elementos HTML comunes (scripts y hojas de estilos) y establece el diseño
de la aplicación. Por ejemplo, al hacer clic en RazorPagesMovie, Inicio, Acerca de o Contacto, verá los
mismos elementos. Los elementos comunes incluyen el menú de navegación de la parte superior y el
encabezado de la parte inferior de la ventana. Consulte Diseño para obtener más información.
El archivo _ViewStart.cshtml establece la propiedad Layout de las páginas de Razor que para usar el archivo
_Layout.cshtml. Vea Layout (Diseño) para más información.
El archivo _ViewImports.cshtml contiene directivas de Razor que se importan en cada página de Razor.
Consulte Importing Shared Directives (Importar directivas compartidas) para obtener más información.
El archivo _ValidationScriptsPartial.cshtml proporciona una referencia de los scripts de validación de jQuery.
Al agregar las páginas Create y Edit más adelante en el tutorial, se usará el archivo
_ValidationScriptsPartial.cshtml.
Las páginas About , Contact y Index son páginas básicas que puede usar para iniciar una aplicación. La
página Error se usa para mostrar información de errores.
S IG U IE N T E : A D IC IÓ N D E U N
M ODELO
Agregar un modelo a una aplicación de páginas de
Razor en ASP.NET Core
25/05/2018 • 7 minutes to read • Edit Online
using System;
namespace RazorPagesMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
}
namespace RazorPagesMovie.Models
{
public class MovieContext : DbContext
{
public MovieContext(DbContextOptions<MovieContext> options)
: base(options)
{
}
El código anterior crea una propiedad DbSet para el conjunto de entidades. En la terminología de Entity
Framework, un conjunto de entidades suele corresponderse con una tabla de base de datos, mientras que una
entidad lo hace con una fila de la tabla.
Agregar una cadena de conexión de base de datos
Agregue una cadena de conexión al archivo appsettings.json.
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
},
"ConnectionStrings": {
"MovieContext": "Server=(localdb)\\mssqllocaldb;Database=Movie-
1;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
services.AddDbContext<MovieContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MovieContext")));
services.AddMvc();
}
Se pueden usar los siguientes comandos de la CLI de .NET Core de forma alternativa:
El comando Install-Package instala las herramientas necesarias para ejecutar el motor de scaffolding.
El comando Add-Migration genera el código para crear el esquema de base de datos inicial. El esquema se basa
en el modelo especificado en DbContext (en el archivo Models/MovieContext.cs). El argumento Initial se usa
para asignar un nombre a las migraciones. Puede usar cualquier nombre, pero se suele elegir uno que describa la
migración. Vea Introduction to migrations (Introducción a las migraciones) para obtener más información.
El comando Update-Database ejecuta el método Up en el archivo Migrations/<time-stamp>_InitialCreate.cs, con
lo que se crea la base de datos.
Aplicar scaffolding al modelo de película
Ejecute lo siguiente desde la línea de comandos (en el directorio del proyecto que contiene los archivos
Program.cs, Startup.cs y .csproj):
Si se produce un error:
No executable found matching command "dotnet-aspnet-codegenerator"
El error anterior se produce cuando el usuario se encuentra en el directorio incorrecto. Abra un shell de comandos
en el directorio del proyecto (el directorio que contiene los archivos Program.cs, Startup.cs y .csproj) y, después,
ejecute el comando anterior.
Si se produce un error:
PARÁMETRO DESCRIPTION
Prueba de la aplicación
Ejecute la aplicación y anexe /Movies a la dirección URL en el explorador ( http://localhost:port/movies ).
Pruebe el vínculo Crear.
Pruebe los vínculos Editar, Detalles y Eliminar.
Si obtiene una excepción SQL, verifique que haya ejecuto las migraciones y que la base de datos esté actualizada:
En el tutorial siguiente se explican los archivos creados mediante scaffolding.
A N T E R IO R : S IG U IE N T E : P Á G IN A S D E R A Z O R C R E A D A S M E D IA N T E
IN T R O D U C C IÓ N S C A F F O L D IN G
Páginas de Razor con scaffolding en ASP.NET Core
25/06/2018 • 12 minutes to read • Edit Online
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Movie) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Razor puede realizar la transición de HTML a C# o a un marcado específico de Razor. Cuando el símbolo @ va
seguido de una palabra clave reservada de Razor, realiza una transición a un marcado específico de Razor; en
caso contrario, realiza la transición a C#.
La directiva de Razor @page convierte el archivo en una acción — de MVC, lo que significa que puede controlar
las solicitudes. @page debe ser la primera directiva de Razor de una página. @page es un ejemplo de la transición
a un marcado específico de Razor. Vea Razor syntax (Sintaxis de Razor) para más información.
Examine la expresión lambda usada en la siguiente aplicación auxiliar HTML:
@Html.DisplayNameFor(model => model.Movie[0].Title))
La aplicación auxiliar HTML DisplayNameFor inspecciona la propiedad Title a la que se hace referencia en la
expresión lambda para determinar el nombre para mostrar. La expresión lambda se inspecciona, no se evalúa.
Esto significa que no hay ninguna infracción de acceso si model , model.Movie o model.Movie[0] son null o
están vacíos. Al evaluar la expresión lambda (por ejemplo, con @Html.DisplayFor(modelItem => item.Title) ), se
evalúan los valores de propiedad del modelo.
La directiva @model
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
La directiva @model especifica el tipo del modelo que se pasa a la página de Razor. En el ejemplo anterior, la línea
@model permite que la clase derivada de PageModel esté disponible en la página de Razor. El modelo se usa en las
aplicaciones auxiliares HTML @Html.DisplayNameFor y @Html.DisplayName de la página.
Propiedades ViewData y Layout
Observe el código siguiente:
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
@{
ViewData["Title"] = "Index";
}
El código resaltado anterior es un ejemplo de Razor con una transición a C#. Los caracteres { y } delimitan un
bloque de código de C#.
La clase base PageModel tiene una propiedad de diccionario ViewData que se puede usar para agregar datos que
quiera pasar a una vista. Puede agregar objetos al diccionario ViewData con un patrón de clave/valor. En el
ejemplo anterior, se agrega la propiedad "Title" al diccionario ViewData . La propiedad "Title" se usa en el archivo
Pages/_Layout.cshtml. En el siguiente marcado se muestran las primeras líneas del archivo Pages/_Layout.cshtml.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - RazorPagesMovie</title>
La línea @*Markup removed for brevity.*@ es un comentario de Razor. A diferencia de los comentarios HTML (
<!-- --> ), los comentarios de Razor no se envían al cliente.
Ejecute la aplicación y pruebe los vínculos del proyecto (Inicio, Acerca de, Contacto, Crear, Editar y Eliminar).
Cada página establece el título, que puede ver en la pestaña del explorador. Al marcar una página, se usa el título
para el marcador. Pages/Index.cshtml y Pages/Movies/Index.cshtml actualmente tienen el mismo título, pero
puede modificarlos para que tengan valores diferentes.
La propiedad Layout se establece en el archivo Pages/_ViewStart.cshtml:
@{
Layout = "_Layout";
}
El marcado anterior establece el archivo de diseño en Pages/_Layout.cshtml para todos los archivos de Razor en la
carpeta Pages. Vea Layout (Diseño) para más información.
Actualizar el diseño
Cambie el elemento <title> del archivo Pages/_Layout.cshtml para usar una cadena más corta.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Movie</title>
El elemento delimitador anterior es una aplicación auxiliar de etiquetas. En este caso, se trata de la aplicación
auxiliar de etiquetas Anchor. El atributo y valor de la aplicación auxiliar de etiquetas asp-page="/Movies/Index"
crea un vínculo a la página de Razor /Movies/Index .
Guarde los cambios y pruebe la aplicación haciendo clic en el vínculo RpMovie. Consulte el archivo
_Layout.cshtml en GitHub.
Modelo de página Crear
Examine el modelo de página Pages/Movies/Create.cshtml.cs:
// Unused usings removed.
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesMovie.Models;
using System.Threading.Tasks;
namespace RazorPagesMovie.Pages.Movies
{
public class CreateModel : PageModel
{
private readonly RazorPagesMovie.Models.MovieContext _context;
[BindProperty]
public Movie Movie { get; set; }
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
}
El método OnGet inicializa cualquier estado necesario para la página. La página Crear no tiene ningún estado
para inicializar. El método Page crea un objeto PageResult que representa la página Create.cshtml.
La propiedad Movie usa el atributo [BindProperty] para participar en el enlace de modelos. Cuando el
formulario de creación publica los valores del formulario, el tiempo de ejecución de ASP.NET Core enlaza los
valores publicados con el modelo Movie .
El método OnPostAsync se ejecuta cuando la página publica los datos del formulario:
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
Si hay algún error de modelo, se vuelve a mostrar el formulario, junto con los datos del formulario publicados. La
mayoría de los errores de modelo se pueden capturar en el cliente antes de que se publique el formulario. Un
ejemplo de un error de modelo consiste en publicar un valor para el campo de fecha que no se puede convertir en
una fecha. Más adelante en el tutorial volveremos a hablar de la validación del lado cliente y de la validación de
modelos.
Si no hay ningún error de modelo, los datos se guardan y el explorador se redirige a la página Índice.
Página de Razor Crear
Examine el archivo de la página de Razor Pages/Movies/Create.cshtml:
@page
@model RazorPagesMovie.Pages.Movies.CreateModel
@{
ViewData["Title"] = "Create";
}
<h2>Create</h2>
<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.ReleaseDate" class="control-label"></label>
<input asp-for="Movie.ReleaseDate" class="form-control" />
<span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Genre" class="control-label"></label>
<input asp-for="Movie.Genre" class="form-control" />
<span asp-validation-for="Movie.Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Price" class="control-label"></label>
<input asp-for="Movie.Price" class="form-control" />
<span asp-validation-for="Movie.Price" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Visual Studio muestra la etiqueta <form method="post"> con una fuente diferenciada que se aplica a las
aplicaciones auxiliares de etiquetas:
El elemento <form method="post"> es una aplicación auxiliar de etiquetas de formulario. La aplicación auxiliar de
etiquetas de formulario incluye automáticamente un token antifalsificación.
El motor de scaffolding crea un marcado de Razor para cada campo del modelo (excepto el identificador) similar
al siguiente:
services.AddDbContext<MovieContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MovieContext")));
services.AddMvc();
}
El sistema Configuración de ASP.NET Core lee el elemento ConnectionString . Para el desarrollo local, obtiene la
cadena de conexión del archivo appsettings.json:
"ConnectionStrings": {
"MovieContext": "Server=(localdb)\\mssqllocaldb;Database=Movie-
1;Trusted_Connection=True;MultipleActiveResultSets=true"
}
Al implementar la aplicación en un servidor de producción o de prueba, puede usar una variable de entorno u
otro enfoque para establecer la cadena de conexión en una instancia real de SQL Server. Para más información,
vea Configuración.
Observe el icono de llave junto a ID . De forma predeterminada, EF crea una propiedad denominada ID para la
clave principal.
Haga clic con el botón derecho en la tabla Movie y seleccione Ver datos:
Inicialización de la base de datos
Cree una nueva clase denominada SeedData en la carpeta Models. Reemplace el código generado con el
siguiente:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;
namespace RazorPagesMovie.Models
{
public static class SeedData
{
public static void Initialize(IServiceProvider serviceProvider)
{
using (var context = new MovieContext(
serviceProvider.GetRequiredService<DbContextOptions<MovieContext>>()))
{
// Look for any movies.
if (context.Movie.Any())
{
return; // DB has been seeded
}
context.Movie.AddRange(
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-2-12"),
Genre = "Romantic Comedy",
Price = 7.99M
},
new Movie
{
Title = "Ghostbusters",
ReleaseDate = DateTime.Parse("1984-3-13"),
Genre = "Comedy",
Price = 8.99M
},
new Movie
{
Title = "Ghostbusters 2",
ReleaseDate = DateTime.Parse("1986-2-23"),
Genre = "Comedy",
Price = 9.99M
},
new Movie
{
Title = "Rio Bravo",
ReleaseDate = DateTime.Parse("1959-4-15"),
Genre = "Western",
Price = 3.99M
}
);
context.SaveChanges();
}
}
}
}
Si hay alguna película en la base de datos, se devuelve el inicializador y no se agrega ninguna película.
if (context.Movie.Any())
{
return; // DB has been seeded.
}
Agregar el inicializador
Agregue el inicializador al final del método Main del archivo Program.cs:
namespace RazorPagesMovie
{
public class Program
{
public static void Main(string[] args)
{
var host = BuildWebHost(args);
try
{
var context = services.GetRequiredService<MovieContext>();
// requires using Microsoft.EntityFrameworkCore;
context.Database.Migrate();
// Requires using RazorPagesMovie.Models;
SeedData.Initialize(services);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred seeding the DB.");
}
}
host.Run();
}
Prueba de la aplicación
Elimine todos los registros de la base de datos. Puede hacerlo con los vínculos de eliminación en el
explorador o desde SSOX.
Obligue a la aplicación a inicializarse (llame a los métodos de la clase Startup ) para que se ejecute el
método de inicialización. Para forzar la inicialización, se debe detener y reiniciar IIS Express. Puede hacerlo
con cualquiera de los siguientes enfoques:
Haga clic con el botón derecho en el icono Bandeja del sistema de IIS Express del área de
notificación y pulse en Salir o en Detener sitio:
A N T E R IO R : P Á G IN A S D E R A Z O R C O N S IG U IE N T E : A C T U A L IZ A C IÓ N D E L A S
S C A F F O L D IN G P Á G IN A S
Actualizar las páginas generadas en una aplicación
ASP.NET Core
14/05/2018 • 7 minutes to read • Edit Online
namespace RazorPagesMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
Haga clic con el botón derecho en una línea ondulada roja > ** Acciones rápidas y refactorizaciones**.
Las aplicaciones auxiliares de etiquetas permiten que el código de servidor participe en la creación y la
representación de elementos HTML en archivos de Razor. En el código anterior, AnchorTagHelper genera de forma
dinámica el valor del atributo href HTML desde la página de Razor (la ruta es relativa), el elemento asp-page y
el identificador de ruta ( asp-route-id ). Vea Generación de direcciones URL para las páginas para obtener más
información.
Use Ver código fuente en su explorador preferido para examinar el marcado generado. A continuación se
muestra una parte del HTML generado:
<td>
<a href="/Movies/Edit?id=1">Edit</a> |
<a href="/Movies/Details?id=1">Details</a> |
<a href="/Movies/Delete?id=1">Delete</a>
</td>
Los vínculos generados de forma dinámica pasan el identificador de la película con una cadena de consulta (por
ejemplo, http://localhost:5000/Movies/Details?id=2 ).
Actualice las páginas de edición, detalles y eliminación de Razor para usar la plantilla de ruta "{id:int}". Cambie la
directiva de página de cada una de estas páginas de @page a @page "{id:int}" . Ejecute la aplicación y luego vea
el origen. El HTML generado agrega el identificador a la parte de la ruta de acceso de la dirección URL:
<td>
<a href="/Movies/Edit/1">Edit</a> |
<a href="/Movies/Details/1">Details</a> |
<a href="/Movies/Delete/1">Delete</a>
</td>
Una solicitud a la página con la plantilla de ruta "{id:int}" que no incluya el entero devolverá un error HTTP 404
(no encontrado). Por ejemplo, http://localhost:5000/Movies/Details devolverá un error 404. Para que el
identificador sea opcional, anexe ? a la restricción de ruta:
@page "{id:int?}"
_context.Attach(Movie).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!_context.Movie.Any(e => e.ID == Movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
El código anterior solo detecta las excepciones de simultaneidad cuando el primer cliente simultáneo elimina la
película y el segundo cliente simultáneo publica cambios en ella.
Para probar el bloque catch :
Establecer un punto de interrupción en catch (DbUpdateConcurrencyException)
Edite una película.
En otra ventana del explorador, seleccione el vínculo de eliminación de la misma película y luego elimínela.
En la ventana anterior del explorador, publique los cambios en la película.
El código de producción por lo general debería detectar conflictos de simultaneidad cuando dos o más clientes
actualizan un registro de forma simultánea. Vea Administración de conflictos de simultaneidad para más
información.
Revisión de publicaciones y enlaces
Examine el archivo Pages/Movies/Edit.cshtml.cs: [!code-csharp]
Cuando se realiza una solicitud HTTP GET a la página Movies/Edit (por ejemplo,
http://localhost:5000/Movies/Edit/2 ):
[BindProperty]
public Movie Movie { get; set; }
Si hay errores en el estado del modelo (por ejemplo, ReleaseDate no se puede convertir en una fecha), el
formulario se vuelve a publicar con los valores enviados.
Si no hay ningún error en el modelo, se guarda la película.
Los métodos HTTP GET de las páginas de índice, creación y eliminación de Razor siguen un patrón similar. El
método HTTP POST OnPostAsync de la página de creación de Razor sigue un patrón similar al del método
OnPostAsync de la página de edición de Razor.
A N T E R IO R : T R A B A J A R C O N S Q L S E R V E R A D IC IÓ N D E
LOCA LDB BÚSQUEDA
Agregar búsqueda a páginas de Razor de ASP.NET
Core
25/06/2018 • 6 minutes to read • Edit Online
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
La primera línea del método OnGetAsync crea una consulta LINQ para seleccionar las películas:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
El código s => s.Title.Contains() es una expresión lambda. Las lambdas se usan en consultas LINQ basadas en
métodos como argumentos para métodos de operador de consulta estándar, como el método Where o Contains
(usado en el código anterior). Las consultas LINQ no se ejecutan cuando se definen ni cuando se modifican
mediante una llamada a un método (como Where , Contains u OrderBy ). En su lugar, se aplaza la ejecución de la
consulta. Esto significa que la evaluación de una expresión se aplaza hasta que su valor realizado se repite o se
llama al método ToListAsync . Para más información, vea Query Execution (Ejecución de consultas).
Nota: El método Contains se ejecuta en la base de datos, no en el código de C#. La distinción entre mayúsculas y
minúsculas en la consulta depende de la base de datos y la intercalación. En SQL Server, Contains se asigna a
SQL LIKE, que distingue entre mayúsculas y minúsculas. En SQLite, con la intercalación predeterminada, se
distingue entre mayúsculas y minúsculas.
Vaya a la página de películas y anexe una cadena de consulta como ?searchString=Ghost a la dirección URL (por
ejemplo, http://localhost:5000/Movies?searchString=Ghost ). Se muestran las películas filtradas.
Si se agrega la siguiente plantilla de ruta a la página de índice, la cadena de búsqueda se puede pasar como un
segmento de dirección URL (por ejemplo, http://localhost:5000/Movies/Ghost ).
@page "{searchString?}"
La restricción de ruta anterior permite buscar el título como datos de ruta (un segmento de dirección URL ) en
lugar de como un valor de cadena de consulta. El elemento ? de "{searchString?}" significa que se trata de un
parámetro de ruta opcional.
Sin embargo, no se puede esperar que los usuarios modifiquen la dirección URL para buscar una película. En este
paso, se agrega la interfaz de usuario para filtrar las películas. Si ha agregado la restricción de ruta
"{searchString?}" , quítela.
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<form>
<p>
Title: <input type="text" name="SearchString">
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
@*Markup removed for brevity.*@
La etiqueta HTML <form> usa la aplicación auxiliar de etiquetas de formulario. Cuando se envía el formulario, la
cadena de filtro se envía a la página Pages/Movies/Index. Guarde los cambios y pruebe el filtro.
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (!String.IsNullOrEmpty(movieGenre))
{
movies = movies.Where(x => x.Genre == movieGenre);
}
Genres = new SelectList(await genreQuery.Distinct().ToListAsync());
Movie = await movies.ToListAsync();
}
El código siguiente es una consulta LINQ que recupera todos los géneros de la base de datos.
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<form>
<p>
<select asp-for="MovieGenre" asp-items="Model.Genres">
<option value="">All</option>
</select>
<table class="table">
<thead>
A N T E R IO R : A C T U A L IZ A C IÓ N D E S IG U IE N T E : A D IC IÓ N D E U N N U E V O
P Á G IN A S CAM PO
Agregar un campo nuevo a una página de Razor en
ASP.NET Core
14/05/2018 • 7 minutes to read • Edit Online
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<form>
<p>
<select asp-for="MovieGenre" asp-items="Model.Genres">
<option value="">All</option>
</select>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Price)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Rating)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Movie) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.Rating)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
@{
ViewData["Title"] = "Create";
}
<h2>Create</h2>
<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.ReleaseDate" class="control-label"></label>
<input asp-for="Movie.ReleaseDate" class="form-control" />
<span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Genre" class="control-label"></label>
<input asp-for="Movie.Genre" class="form-control" />
<span asp-validation-for="Movie.Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Price" class="control-label"></label>
<input asp-for="Movie.Price" class="form-control" />
<span asp-validation-for="Movie.Price" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Rating" class="control-label"></label>
<input asp-for="Movie.Rating" class="form-control" />
<span asp-validation-for="Movie.Rating" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Este error se debe a que la clase del modelo Movie actualizado es diferente al esquema de la tabla Movie de la
base de datos. (No hay ninguna columna Rating en la tabla de la base de datos).
Este error se puede resolver de varias maneras:
1. Haga que Entity Framework quite de forma automática la base de datos y la vuelva a crear con el nuevo
esquema de la clase del modelo. Este enfoque resulta conveniente al principio del ciclo de desarrollo;
permite desarrollar a la vez y de manera rápida el esquema del modelo y la base de datos. El
inconveniente es que se pierden los datos existentes en la base de datos. No use este enfoque en una base
de datos de producción. El quitar la base de datos en los cambios de esquema y el usar un inicializador
para inicializar automáticamente la base de datos con datos de prueba suele ser una forma productiva de
desarrollar una aplicación.
2. Modifique explícitamente el esquema de la base de datos existente para que coincida con las clases del
modelo. La ventaja de este enfoque es que se conservan los datos. Puede realizar este cambio de forma
manual o mediante la creación de un script de cambio de base de datos.
3. Use Migraciones de Code First para actualizar el esquema de la base de datos.
Para este tutorial, use Migraciones de Code First.
Actualice la clase SeedData para que proporcione un valor para la nueva columna. A continuación se muestra un
cambio de ejemplo, aunque es conveniente realizar este cambio para cada bloque new Movie .
context.Movie.AddRange(
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-2-12"),
Genre = "Romantic Comedy",
Price = 7.99M,
Rating = "R"
},
Add-Migration Rating
Update-Database
Update-Database
Ejecute la aplicación y compruebe que puede crear, editar o mostrar películas con un campo Rating . Si la base
de datos no está inicializada, detenga IIS Express y luego ejecute la aplicación.
A N T E R IO R : A D IC IÓ N D E S IG U IE N T E : A D IC IÓ N D E U N A
BÚSQUEDA V A L ID A C IÓ N
Agregar la validación a una página de Razor de
ASP.NET Core
17/05/2018 • 15 minutes to read • Edit Online
Validación
Un principio clave de desarrollo de software se denomina DRY, por "Don't Repeat Yourself" (Una vez y solo una).
Las páginas de Razor fomentan un tipo de desarrollo en el que la funcionalidad se especifica una vez y se refleja
en la aplicación. DRY puede ayudar a reducir la cantidad de código en una aplicación. DRY hace que el código sea
menos propenso a errores y resulte más fácil de probar y mantener.
La compatibilidad de validación proporcionada por las páginas de Razor y Entity Framework es un buen ejemplo
del principio DRY. Las reglas de validación se especifican mediante declaración en un solo lugar (en la clase del
modelo) y se aplican en toda la aplicación.
Adición de reglas de validación al modelo de película
Abra el archivo Movie.cs. DataAnnotations proporciona un conjunto integrado de atributos de validación que se
aplican mediante declaración a una clase o propiedad. DataAnnotations también contiene atributos de formato
como DataType que ayudan a aplicar formato y no proporcionan validación.
Actualice la clase Movie para aprovechar los atributos de validación Required , StringLength , RegularExpression
y Range .
[Range(1, 100)]
[DataType(DataType.Currency)]
public decimal Price { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")]
[Required]
[StringLength(30)]
public string Genre { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")]
[StringLength(5)]
[Required]
public string Rating { get; set; }
}
Los atributos de validación especifican el comportamiento que se aplica a las propiedades del modelo:
Los atributos Required y MinimumLength indican que una propiedad debe tener un valor. Aun así, nada impide
que el usuario escriba un espacio en blanco para cumplir la restricción de validación de un tipo que acepta
valores NULL. Los tipos de valor que no aceptan valores NULL (como decimal , int , float y DateTime ) son
intrínsecamente necesarios y no necesitan el atributo Required .
El atributo RegularExpression limita los caracteres que el usuario puede escribir. En el código anterior, Genre y
Rating solo deben usar letras (no se permiten espacios en blanco, números ni caracteres especiales).
El atributo Range restringe un valor a un intervalo determinado.
El atributo StringLength establece la longitud máxima de una cadena y, de forma opcional, la longitud mínima.
El que ASP.NET Core aplique automáticamente las reglas de validación ayuda a que una aplicación sea más
sólida. La validación automática en modelos ayuda a proteger la aplicación, ya que no hay que acordarse de su
aplicación cuando se agrega nuevo código.
Interfaz de usuario de error de validación en páginas de Razor
Ejecute la aplicación y vaya a Pages/Movies.
Seleccione el vínculo Crear nuevo. Rellene el formulario con algunos valores no válidos. Cuando la validación de
cliente de jQuery detecta el error, muestra un mensaje de error.
NOTE
Es posible que no pueda escribir comas ni puntos decimales en el campo Price . Para admitir la validación de jQuery en
configuraciones regionales distintas del inglés que usan una coma (",") en lugar de un punto decimal y formatos de fecha
distintos del inglés (EE. UU.), debe tomar medidas para globalizar la aplicación. Para más información, vea Recursos
adicionales. Por ahora, escriba solamente números enteros como 10.
Observe cómo el formulario presenta automáticamente un mensaje de error de validación en cada campo que
contiene un valor no válido. Los errores se aplican al cliente (con JavaScript y jQuery) y al servidor (cuando un
usuario tiene JavaScript deshabilitado).
Una ventaja importante es que no se han necesitado cambios de código en las páginas de creación o edición. Una
vez que DataAnnotations se ha aplicado al modelo, la interfaz de usuario de validación se ha habilitado. Las
páginas de Razor creadas en este tutorial han obtenido automáticamente las reglas de validación (mediante
atributos de validación en las propiedades de la clase del modelo Movie ). Al probar la validación en la página de
edición, se aplica la misma validación.
Los datos del formulario no se publicarán en el servidor hasta que dejen de producirse errores de validación de
cliente. Compruebe que los datos del formulario no se publican mediante uno o varios de los métodos siguientes:
Coloque un punto de interrupción en el método OnPostAsync . Envíe el formulario (seleccione Crear o
Guardar). El punto de interrupción nunca se alcanza.
Use la herramienta Fiddler.
Use las herramientas de desarrollo del explorador para supervisar el tráfico de red.
Validación de servidor
Cuando JavaScript está deshabilitado en el explorador, si se envía el formulario con errores, se publica en el
servidor.
Validación de servidor de prueba opcional:
Deshabilite JavaScript en el explorador. Si no se puede deshabilitar JavaScript en el explorador, pruebe con
otro explorador.
Establezca un punto de interrupción en el método OnPostAsync de la página de creación o edición.
Envíe un formulario con errores de validación.
Compruebe que el estado del modelo no es válido:
if (!ModelState.IsValid)
{
return Page();
}
El código siguiente muestra una parte de la página Create.cshtml a la que se ha aplicado scaffolding
anteriormente en el tutorial. Las páginas de creación y edición la usan para mostrar el formulario inicial y para
volver a mostrar el formulario en caso de error.
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>
La aplicación auxiliar de etiquetas de entrada usa los atributos DataAnnotations y genera los atributos HTML
necesarios para la validación de jQuery en el cliente. La aplicación auxiliar de etiquetas de validación muestra
errores de validación. Para más información, vea Validación.
Las páginas de creación y edición no tienen ninguna regla de validación. Las reglas de validación y las cadenas de
error solo se especifican en la clase Movie . Estas reglas de validación se aplican automáticamente a las páginas de
Razor que editan el modelo Movie .
Cuando es necesario modificar la lógica de validación, se hace únicamente en el modelo. La validación se aplica de
forma coherente en toda la aplicación (la lógica de validación se define en un solo lugar). La validación en un solo
lugar ayuda a mantener limpio el código y facilita su mantenimiento y actualización.
[Range(1, 100)]
[DataType(DataType.Currency)]
public decimal Price { get; set; }
Los atributos DataType solo proporcionan sugerencias para que el motor de vista aplique formato a los datos (y
ofrece atributos como <a> para las direcciones URL y <a href="mailto:EmailAddress.com"> para el correo
electrónico). Use el atributo RegularExpression para validar el formato de los datos. El atributo DataType se usa
para especificar un tipo de datos más específico que el tipo intrínseco de base de datos. Los atributos DataType
no son atributos de validación. En la aplicación de ejemplo solo se muestra la fecha, sin hora.
La enumeración DataType proporciona muchos tipos de datos, como Date, Time, PhoneNumber, Currency,
EmailAddress, etc. El atributo DataType también puede permitir que la aplicación proporcione automáticamente
características específicas del tipo. Por ejemplo, se puede crear un vínculo mailto: para DataType.EmailAddress .
Se puede proporcionar un selector de fecha para DataType.Date en exploradores compatibles con HTML5. Los
atributos DataType emiten atributos HTML 5 data- (se pronuncia "datos dash") para su uso por parte de los
exploradores HTML 5. Los atributos DataType no proporcionan ninguna validación.
DataType.Date no especifica el formato de la fecha que se muestra. De manera predeterminada, el campo de
datos se muestra según los formatos predeterminados basados en el elemento CultureInfo del servidor.
El atributo DisplayFormat se usa para especificar el formato de fecha de forma explícita:
El valor ApplyFormatInEditMode especifica que el formato se debe aplicar cuando el valor se muestra para su
edición. Es posible que no quiera ese comportamiento para algunos campos. Por ejemplo, en los valores de
moneda, probablemente no quiera el símbolo de moneda en la interfaz de usuario de edición.
El atributo DisplayFormat puede usarse por sí mismo, pero normalmente es buena idea usar el atributo DataType
. El atributo DataType transmite la semántica de los datos en contraposición a cómo se representan en una
pantalla y ofrece las siguientes ventajas que no proporciona DisplayFormat:
El explorador puede habilitar características de HTML5 (por ejemplo, mostrar un control de calendario, el
símbolo de moneda adecuado según la configuración regional, vínculos de correo electrónico, etc.).
De forma predeterminada, el explorador presenta los datos con el formato correcto en función de la
configuración regional.
El atributo DataType puede habilitar el marco de trabajo de ASP.NET Core para elegir la plantilla de campo
correcta a fin de presentar los datos. DisplayFormat , si se emplea por sí mismo, usa la plantilla de cadena.
Nota: La validación de jQuery no funciona con el atributo Range ni DateTime . Por ejemplo, el código siguiente
siempre muestra un error de validación de cliente, incluso cuando la fecha está en el intervalo especificado:
Por lo general no se recomienda compilar fechas fijas en los modelos, así que se desaconseja el empleo del
atributo Range y DateTime .
El código siguiente muestra la combinación de atributos en una línea:
[RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$"), StringLength(5)]
public string Rating { get; set; }
}
En Introducción a las páginas de Razor y EF Core se muestran operaciones más avanzadas de EF Core con
páginas de Razor.
Publicar en Azure
Vea Publicar una aplicación web de ASP.NET Core en Azure App Service con Visual Studio para obtener
instrucciones sobre cómo publicar esta aplicación en Azure.
Recursos adicionales
Trabajar con formularios
Globalización y localización
Introducción a las aplicaciones auxiliares de etiquetas
Creación de aplicaciones auxiliares de etiquetas
A N T E R IO R : A D IC IÓ N D E U N N U E V O S IG U IE N T E : C A R G A D E
CAM PO A R C H IV O S
Cargar archivos en una página de Razor en ASP.NET
Core
25/06/2018 • 21 minutes to read • Edit Online
Consideraciones de seguridad
Debe tener precaución al proporcionar a los usuarios la capacidad de cargar archivos en un servidor, ya que los
atacantes podrían ejecutar un ataque por denegación de servicio u otros ataques en un sistema. A continuación se
muestran algunos pasos de seguridad con los que se reduce la probabilidad de sufrir ataques:
Cargue archivos en un área de carga de archivos específica del sistema, con lo que resulta más fácil imponer
medidas de seguridad en el contenido cargado. Al permitir las cargas de archivos, asegúrese de que los
permisos de ejecución están deshabilitados en la ubicación de carga.
Use un nombre de archivo seguro determinado por la aplicación, y no por la entrada del usuario o el nombre
de archivo del archivo cargado.
Permita únicamente un conjunto específico de extensiones de archivo aprobadas.
Compruebe que se llevan a cabo comprobaciones de cliente en el servidor. Las comprobaciones de cliente son
fáciles de sortear.
Compruebe el tamaño de la carga y evite las cargas de mayor tamaño de lo esperado.
Ejecute un escáner de virus/malware en el contenido cargado.
WARNING
La carga de código malintencionado en un sistema suele ser el primer paso para ejecutar código que puede:
Tomar todo el poder en un sistema.
Sobrecargar un sistema de manera que se producen errores generales del sistema.
Poner en peligro los datos del usuario o del sistema.
Aplicar grafitis a una interfaz pública.
using Microsoft.AspNetCore.Http;
using System.ComponentModel.DataAnnotations;
namespace RazorPagesMovie.Models
{
public class FileUpload
{
[Required]
[Display(Name="Title")]
[StringLength(60, MinimumLength = 3)]
public string Title { get; set; }
[Required]
[Display(Name="Public Schedule")]
public IFormFile UploadPublicSchedule { get; set; }
[Required]
[Display(Name="Private Schedule")]
public IFormFile UploadPrivateSchedule { get; set; }
}
}
La clase tiene una propiedad para el título de la programación y otra para cada una de las dos versiones de la
programación. Las tres propiedades son necesarias y el título debe tener entre 3 y 60 caracteres.
using System;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using RazorPagesMovie.Models;
namespace RazorPagesMovie.Utilities
{
public class FileHelpers
{
public static async Task<string> ProcessFormFile(IFormFile formFile, ModelStateDictionary modelState)
{
var fieldDisplayName = string.Empty;
if (property != null)
{
var displayAttribute =
var displayAttribute =
property.GetCustomAttribute(typeof(DisplayAttribute)) as DisplayAttribute;
if (displayAttribute != null)
{
fieldDisplayName = $"{displayAttribute.Name} ";
}
}
if (formFile.ContentType.ToLower() != "text/plain")
{
modelState.AddModelError(formFile.Name,
$"The {fieldDisplayName}file ({fileName}) must be a text file.");
}
return string.Empty;
}
}
}
return RedirectToPage("./Index");
}
El proceso de trabajo debe tener permisos de escritura en la ubicación especificada por filePath .
NOTE
filePath debe incluir el nombre de archivo. Si no se ha proporcionado un nombre de archivo, se produce una excepción
UnauthorizedAccessException en tiempo de ejecución.
WARNING
Los archivos cargados nunca deben persistir en el mismo árbol de directorio que la aplicación.
En el código de ejemplo no se proporciona ningún tipo de protección de servidor frente a cargas de archivos
malintencionados. Vea los siguientes recursos para más información sobre cómo reducir el área expuesta de ataques al
aceptar archivos de los usuarios:
Unrestricted File Upload (Carga de archivos sin restricciones)
Azure Security: asegúrese de que los controles adecuados estén en vigor al aceptar archivos de usuarios
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace RazorPagesMovie.Models
{
public class Schedule
{
public int ID { get; set; }
public string Title { get; set; }
La clase usa los atributos Display y DisplayFormat , que generan títulos descriptivos y formato cuando se
representan los datos de programación.
Actualización de MovieContext
Especifique DbSet en MovieContext (Models/MovieContext.cs) para las programaciones:
using Microsoft.EntityFrameworkCore;
namespace RazorPagesMovie.Models
{
public class MovieContext : DbContext
{
public MovieContext(DbContextOptions<MovieContext> options)
: base(options)
{
}
En la PMC, ejecute los siguientes comandos. Estos comandos agregan una tabla Schedule a la base de datos:
Add-Migration AddScheduleTable
Update-Database
@page
@model RazorPagesMovie.Pages.Schedules.IndexModel
@{
ViewData["Title"] = "Schedules";
}
<h2>Schedules</h2>
<hr />
<h3>Upload Schedules</h3>
<div class="row">
<div class="col-md-4">
<form method="post" enctype="multipart/form-data">
<div class="form-group">
<label asp-for="FileUpload.Title" class="control-label"></label>
<input asp-for="FileUpload.Title" type="text" class="form-control" />
<span asp-validation-for="FileUpload.Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="FileUpload.UploadPublicSchedule" class="control-label"></label>
<input asp-for="FileUpload.UploadPublicSchedule" type="file" class="form-control"
style="height:auto" />
<span asp-validation-for="FileUpload.UploadPublicSchedule" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="FileUpload.UploadPrivateSchedule" class="control-label"></label>
<input asp-for="FileUpload.UploadPrivateSchedule" type="file" class="form-control"
style="height:auto" />
<span asp-validation-for="FileUpload.UploadPrivateSchedule" class="text-danger"></span>
</div>
<input type="submit" value="Upload" class="btn btn-default" />
</form>
</div>
</div>
<h3>Loaded Schedules</h3>
<table class="table">
<thead>
<tr>
<th></th>
<th>
@Html.DisplayNameFor(model => model.Schedule[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Schedule[0].UploadDT)
</th>
<th class="text-center">
@Html.DisplayNameFor(model => model.Schedule[0].PublicScheduleSize)
</th>
<th class="text-center">
@Html.DisplayNameFor(model => model.Schedule[0].PrivateScheduleSize)
</th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Schedule) {
<tr>
<td>
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.UploadDT)
</td>
<td class="text-center">
@Html.DisplayFor(modelItem => item.PublicScheduleSize)
</td>
<td class="text-center">
@Html.DisplayFor(modelItem => item.PrivateScheduleSize)
</td>
</tr>
}
</tbody>
</table>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Cada grupo del formulario incluye una <etiqueta> que muestra el nombre de cada propiedad de clase. Los
atributos Display del modelo FileUpload proporcionan los valores de presentación de las etiquetas. Por ejemplo,
el nombre para mostrar de la propiedad UploadPublicSchedule se establece con
[Display(Name="Public Schedule")] y, por tanto, muestra "Programación pública" en la etiqueta cuando se
presenta el formulario.
Cada grupo del formulario incluye un <intervalo> de validación. Si la entrada del usuario no cumple los
atributos de propiedad establecidos en la clase FileUpload o si se produce un error en alguna de las
comprobaciones de validación del archivo del método ProcessFormFile , no se valida el modelo. Cuando se
produce un error en la validación del modelo, se presenta un útil mensaje de validación al usuario. Por ejemplo, la
propiedad Title se anota con [Required] y [StringLength(60, MinimumLength = 3)] . Si el usuario no proporciona
un título, recibe un mensaje que indica que se necesita un valor. Si el usuario especifica un valor de menos de tres
caracteres o de más de sesenta caracteres, recibe un mensaje que indica que el valor tiene una longitud incorrecta.
Si se proporciona un archivo sin contenido, aparece un mensaje que indica que el archivo está vacío.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;
using RazorPagesMovie.Utilities;
namespace RazorPagesMovie.Pages.Schedules
{
public class IndexModel : PageModel
{
private readonly RazorPagesMovie.Models.MovieContext _context;
[BindProperty]
public FileUpload FileUpload { get; set; }
var publicScheduleData =
await FileHelpers.ProcessFormFile(FileUpload.UploadPublicSchedule, ModelState);
var privateScheduleData =
await FileHelpers.ProcessFormFile(FileUpload.UploadPrivateSchedule, ModelState);
_context.Schedule.Add(schedule);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
}
[BindProperty]
public FileUpload FileUpload { get; set; }
El modelo además usa una lista de las programaciones ( IList<Schedule> ) para mostrar las programaciones
almacenadas en la base de datos en la página:
Cuando se carga la página con OnGetAsync , Schedules se rellena a partir de la base de datos y se usa para
generar una tabla HTML de programaciones cargadas:
var publicScheduleData =
await FileHelpers.ProcessSchedule(FileUpload.UploadPublicSchedule, ModelState);
var privateScheduleData =
await FileHelpers.ProcessSchedule(FileUpload.UploadPrivateSchedule, ModelState);
_context.Schedule.Add(schedule);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
@{
ViewData["Title"] = "Delete Schedule";
}
<h2>Delete Schedule</h2>
<form method="post">
<input type="hidden" asp-for="Schedule.ID" />
<input type="submit" value="Delete" class="btn btn-default" /> |
<a asp-page="./Index">Back to List</a>
</form>
</div>
El modelo de página (Delete.cshtml.cs) carga una sola programación identificada por id en los datos de ruta de la
solicitud. Agregue el archivo Delete.cshtml.cs a la carpeta Schedules:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;
namespace RazorPagesMovie.Pages.Schedules
{
public class DeleteModel : PageModel
{
private readonly RazorPagesMovie.Models.MovieContext _context;
[BindProperty]
public Schedule Schedule { get; set; }
if (Schedule == null)
{
return NotFound();
}
return Page();
}
if (Schedule != null)
{
_context.Schedule.Remove(Schedule);
await _context.SaveChangesAsync();
}
return RedirectToPage("./Index");
}
}
}
if (Schedule != null)
{
_context.Schedule.Remove(Schedule);
await _context.SaveChangesAsync();
}
return RedirectToPage("./Index");
}
La selección del botón Cargar sin rellenar ninguno de los campos infringe los atributos [Required] en el modelo.
ModelState no es válido. Los mensajes de error de validación se muestran al usuario:
Escriba dos letras en el campo Título. El mensaje de validación cambia para indicar que el título debe tener entre
3 y 60 caracteres:
Cuando se cargan una o más programaciones, la sección Programaciones cargadas presenta las
programaciones cargadas:
El usuario puede hacer clic en el vínculo Eliminar desde allí para llegar a la vista de confirmación de eliminación,
donde tiene una oportunidad de confirmar o cancelar la operación de eliminación.
Solución de problemas
Para más información de solución de problemas de carga de IFormFile , vea Cargas de archivos en ASP.NET
Core: Solución de problemas.
Gracias por seguir esta introducción a las páginas de Razor. Le agradeceremos cualquier comentario. Introducción
a MVC y EF Core es un excelente artículo de seguimiento de este tutorial.
Recursos adicionales
Cargas de archivos en ASP.NET Core
IFormFile
A N T E R IO R :
V A L ID A C IÓ N
Crear una aplicación web con ASP.NET Core MVC en
Windows con Visual Studio
11/04/2018 • 2 minutes to read • Edit Online
En este tutorial se muestra el desarrollo web de ASP.NET Core MVC con controladores y vistas. Las páginas de
Razor son una nueva alternativa en ASP.NET Core 2.0 y posteriores, un modelo de programación basado en
páginas que facilita la compilación de interfaces de usuario web y hace que sean más productivas. Se recomienda
probar el tutorial de las páginas de Razor antes que la versión MVC. El tutorial de las páginas de Razor:
Es el método preferido para el desarrollo de nuevas aplicaciones.
Es más fácil de seguir.
Abarca más características.
Si elige este tutorial en lugar de la versión de páginas de Razor, le agradeceremos que nos explique el motivo en
este problema de GitHub.
Hay tres versiones de este tutorial:
Windows: Esta serie
macOS: Creación de una aplicación de ASP.NET Core MVC con Visual Studio para Mac
macOS, Linux y Windows: Crear una aplicación de ASP.NET Core MVC con Visual Studio Code La serie de
tutoriales incluye lo siguiente:
1. Introducción
2. Agregar un controlador
3. Agregar una vista
4. Agregar un modelo
5. Trabajar con SQL Server LocalDB
6. Vistas y métodos de controlador
7. Agregar búsqueda
8. Agregar un campo nuevo
9. Agregar validación
10. Examinar los métodos Details y Delete
Introducción a ASP.NET Core MVC y Visual Studio
17/05/2018 • 6 minutes to read • Edit Online
Visual Studio inicia IIS Express y ejecuta la aplicación. Tenga en cuenta que en la barra de direcciones aparece
localhost:port# (y no algo como example.com ). Esto es así porque localhost es el nombre de host estándar
del equipo local. Cuando Visual Studio crea un proyecto web, se usa un puerto aleatorio para el servidor web.
En la imagen anterior, el número de puerto es el 5000. La dirección URL del explorador es localhost:5000 . Al
ejecutar la aplicación verá otro puerto distinto.
Iniciar la aplicación con CTRL+F5 (modo de no depuración) le permite efectuar cambios en el código,
guardar el archivo, actualizar el explorador y ver los cambios de código. Muchos desarrolladores prefieren
usar el modo de no depuración para iniciar la aplicación rápidamente y ver los cambios.
Puede iniciar la aplicación en modo de depuración o en modo de no depuración desde el elemento de menú
Depurar:
La plantilla predeterminada proporciona los vínculos Inicio, Acerca de y Contacto, totalmente funcionales. En
la imagen del explorador anterior no se muestran estos vínculos. Según el tamaño del explorador, tendrá que
hacer clic en el icono de navegación para que se muestren.
Si iba a efectuar una ejecución en modo de depuración, pulse MAYÚS -F5 para detener la depuración.
En la siguiente sección de este tutorial obtendrá información sobre MVC y empezará a escribir código.
S IG U IE N T E
Agregar un controlador a una aplicación de ASP.NET
Core MVC
25/06/2018 • 10 minutes to read • Edit Online
namespace MvcMovie.Controllers
{
public class HelloWorldController : Controller
{
//
// GET: /HelloWorld/
//
// GET: /HelloWorld/Welcome/
Cada método public en un controlador puede ser invocado como un punto de conexión HTTP. En el ejemplo
anterior, ambos métodos devuelven una cadena. Observe los comentarios delante de cada método.
Un extremo HTTP es una dirección URL que se puede usar como destino en la aplicación web, como por ejemplo
http://localhost:1234/HelloWorld . Combina el protocolo usado HTTP , la ubicación de red del servidor web
(incluido el puerto TCP ) localhost:1234 y el URI de destino HelloWorld .
El primer comentario dice que se trata de un método HTTP GET que se invoca mediante la anexión de
"/HelloWorld/" a la dirección URL base. El segundo comentario dice que se trata de un método HTTP GET que se
invoca mediante la anexión de "/HelloWorld/Welcome/" a la dirección URL. Más adelante en el tutorial usaremos
el motor de scaffolding para generar métodos HTTP POST .
Ejecute la aplicación en modo de no depuración y anexione "HelloWorld" a la ruta de acceso en la barra de
direcciones. El método Index devuelve una cadena.
MVC invoca las clases del controlador (y los métodos de acción que contienen) en función de la URL entrante. La
lógica de enrutamiento de URL predeterminada que usa MVC emplea un formato similar al siguiente para
determinar qué código se debe invocar:
/[Controller]/[ActionName]/[Parameters]
Modifique el código para pasar cierta información del parámetro desde la dirección URL al controlador. Por
ejemplo: /HelloWorld/Welcome?name=Rick&numtimes=4 . Cambie el método Welcome para que incluya dos parámetros,
como se muestra en el código siguiente.
// GET: /HelloWorld/Welcome/
// Requires using System.Text.Encodings.Web;
public string Welcome(string name, int numTimes = 1)
{
return HtmlEncoder.Default.Encode($"Hello {name}, NumTimes is: {numTimes}");
}
El código anterior:
Usa la característica de parámetro opcional de C# para indicar que el parámetro numTimes tiene el valor
predeterminado 1 si no se pasa ningún valor para ese parámetro.
Usa HtmlEncoder.Default.Encode para proteger la aplicación de entradas malintencionadas (es decir,
JavaScript).
Usa cadenas interpoladas.
Ejecute la aplicación y navegue a:
http://localhost:xxxx/HelloWorld/Welcome?name=Rick&numtimes=4
(Reemplace xxxx con el número de puerto). Puede probar valores diferentes para name y numtimes en la dirección
URL. El sistema de enlace de modelos de MVC asigna automáticamente los parámetros con nombre de la cadena
de consulta en la barra de dirección a los parámetros del método. Vea Model Binding (Enlace de modelos) para
más información.
En la ilustración anterior, el segmento de dirección URL ( Parameters ) no se usa, y los parámetros name y
numTimes se pasan como cadenas de consulta. El ? (signo de interrogación) en la dirección URL anterior es un
separador y le siguen las cadenas de consulta. El carácter & separa las cadenas de consulta.
Reemplace el método Welcome con el código siguiente:
Esta vez el tercer segmento de dirección URL coincide con el parámetro de ruta id . El método Welcome contiene
un parámetro id que coincide con la plantilla de dirección URL en el método MapRoute . El ? del final (en id? )
indica que el parámetro id es opcional.
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
En estos ejemplos, el controlador ha realizado la parte "VC" de MVC, es decir, el trabajo de vista y de controlador.
El controlador devuelve HTML directamente. Por lo general, no es aconsejable que los controles devuelvan HTML
directamente, porque resulta muy complicado de programar y mantener. En su lugar, se suele usar un archivo de
plantilla de vista de Razor independiente para ayudar a generar la respuesta HTML. Haremos esto en el siguiente
tutorial.
En el modo de no depuración (Ctrl+F5) de Visual Studio no es necesario compilar la aplicación después de
cambiar el código. Solo tiene que guardar el archivo y actualizar el explorador para ver los cambios.
A N T E R IO R S IG U IE N T E
Adición de una vista en una aplicación de ASP.NET
Core MVC
25/06/2018 • 14 minutes to read • Edit Online
El código anterior devuelve un objeto View . Usa una plantilla de vista para generar una respuesta HTML al
explorador. Los métodos de controlador (también conocidos como métodos de acción), como el método Index
anterior, suelen devolver IActionResult o una clase derivada de ActionResult , en lugar de un tipo como una
cadena.
Haga clic con el botón derecho en la carpeta Vistas, haga clic en Agregar > Nueva carpeta y asigne a la
carpeta el nombre HelloWorld.
Haga clic con el botón derecho en la carpeta Views/HelloWorld y, luego, haga clic en Agregar > Nuevo
elemento.
En el cuadro de diálogo Agregar nuevo elemento - MvcMovie
En el cuadro de búsqueda situado en la esquina superior derecha, escriba Vista.
Pulse Vista de Razor.
En el cuadro Nombre, cambie el nombre si es necesario por Index.cshtml.
Pulse Agregar.
Reemplace el contenido del archivo de vista de Razor Views/HelloWorld/Index.cshtml con lo siguiente:
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
Si la ventana del explorador es pequeña (por ejemplo en un dispositivo móvil), es conveniente que alterne (pulse)
el botón de navegación de arranque en la esquina superior derecha para ver los vínculos Home (Inicio), About
(Acerca de) y Contact (Contacto).
Cambiar vistas y páginas de diseño
Pulse los vínculos de menú: MvcMovie (Película de MVC ), Home (Inicio), About (Acerca de). Cada página
muestra el mismo diseño de menú. El diseño de menú se implementa en el archivo Views/Shared/_Layout.cshtml.
Abra el archivo Views/Shared/_Layout.cshtml.
Las plantillas de diseño permiten especificar el diseño del contenedor HTML del sitio en un solo lugar y, después,
aplicarlo en varias páginas del sitio. Busque la línea @RenderBody() . RenderBody es un marcador de posición
donde se mostrarán todas las páginas específicas de vista que cree, encapsuladas en la página de diseño. Por
ejemplo, si selecciona el vínculo About (Acerca de), la vista Views/Home/About.cshtml se representa dentro
del método RenderBody .
[!code-html]
[!code-html]
WARNING
Aún no hemos implementado el controlador Movies , por lo que si hace clic en ese vínculo, obtendrá un error 404 (no
encontrado).
Guarde los cambios y pulse en el vínculo About (Acerca de). Observe cómo el título de la pestaña del explorador
muestra ahora About - Movie App (Acerca de - Aplicación de película) en lugar de About - Mvc Movie (Acerca
de - Aplicación de MVC ):
Pulse el vínculo Contacto y observe que el texto del título y el delimitador también muestran Movie App.
Hemos realizado el cambio una vez en la plantilla de diseño y hemos conseguido que todas las páginas del sitio
reflejen el nuevo texto de vínculo y el nuevo título.
Examine el archivo Views/_ViewStart.cshtml:
@{
Layout = "_Layout";
}
Haremos que sean algo diferentes para que pueda ver qué parte del código cambia cada área de la aplicación.
@{
ViewData["Title"] = "Movie List";
}
En el código anterior, ViewData["Title"] = "Movie List"; establece la propiedad Title del diccionario ViewData
en "Movie List" (Lista de películas). La propiedad Title se usa en el elemento HTML <title> en la página de
diseño:
Guarde el cambio y navegue a http://localhost:xxxx/HelloWorld . Tenga en cuenta que el título del explorador, el
encabezado principal y los encabezados secundarios han cambiado. (Si no ve los cambios en el explorador, es
posible que esté viendo contenido almacenado en caché. Presione Ctrl+F5 en el explorador para forzar que se
cargue la respuesta del servidor). El título del explorador se crea con ViewData["Title"] , que se definió en la
plantilla de vista Index.cshtml y el texto "- Movie App" (-Aplicación de película) que se agregó en el archivo de
diseño.
Observe también cómo el contenido de la plantilla de vista Index.cshtml se fusionó con la plantilla de vista
Views/Shared/_Layout.cshtml y se envió una única respuesta HTML al explorador. Con las plantillas de diseño es
realmente fácil hacer cambios para que se apliquen en todas las páginas de la aplicación. Para saber más, vea
Layout (Diseño).
Nuestra pequeña cantidad de "datos", en este caso, el mensaje "Hello from our View Template!" (Hola desde
nuestra plantilla de vista), están codificados de forma rígida. La aplicación de MVC tiene una "V" (vista) y ha
obtenido una "C" (controlador), pero todavía no tiene una "M" (modelo).
using Microsoft.AspNetCore.Mvc;
using System.Text.Encodings.Web;
namespace MvcMovie.Controllers
{
public class HelloWorldController : Controller
{
public IActionResult Index()
{
return View();
}
return View();
}
}
}
@{
ViewData["Title"] = "Welcome";
}
<h2>Welcome</h2>
<ul>
@for (int i = 0; i < (int)ViewData["NumTimes"]; i++)
{
<li>@ViewData["Message"]</li>
}
</ul>
Los datos se toman de la dirección URL y se pasan al controlador mediante el enlazador de modelos MVC. El
controlador empaqueta los datos en un diccionario ViewData y pasa ese objeto a la vista. Después, la vista
representa los datos como HTML en el explorador.
En el ejemplo anterior, usamos el diccionario ViewData para pasar datos del controlador a una vista. Más adelante
en el tutorial usaremos un modelo de vista para pasar datos de un controlador a una vista. El enfoque del modelo
de vista que consiste en pasar datos suele ser más preferible que el enfoque de diccionario ViewData . Para saber
más, vea ViewModel vs ViewData vs ViewBag vs TempData vs Session in MVC (ViewModel, ViewData, ViewBag,
TempData y Session en MVC ).
Bueno, todo esto era un tipo de "M" para el modelo, pero no el tipo de base de datos. Vamos a aprovechar lo que
hemos aprendido para crear una base de datos de películas.
A N T E R IO R S IG U IE N T E
Agregar un modelo a una aplicación de ASP.NET
Core MVC
14/05/2018 • 15 minutes to read • Edit Online
using System;
namespace MvcMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
}
Scaffolding de un controlador
En el Explorador de soluciones, haga clic con el botón derecho en la carpeta Controladores > Agregar >
Controlador.
La creación automática del contexto de base de datos y de vistas y métodos de acción CRUD (crear, leer, actualizar
y eliminar) se conoce como scaffolding. Pronto contará con una aplicación web totalmente funcional que le
permitirá administrar una base de datos de películas.
Si ejecuta la aplicación y hace clic en el vínculo Mvc Movie, aparece un error similar al siguiente:
An unhandled exception occurred while processing the request.
SqlException: Cannot open database "MvcMovieContext-<GUID removed>" requested by the login. The login failed.
Login failed for user 'Rick'.
Debe crear la base de datos y para ello usar la característica Migraciones de EF Core. Las migraciones permiten
crear una base de datos que coincide con el modelo de datos y actualizan el esquema de base de datos cuando
cambia el modelo de datos.
Install-Package Microsoft.EntityFrameworkCore.Tools
Add-Migration Initial
Update-Database
Nota: Si recibe un error con el comando Install-Package , abra el administrador de paquetes NuGet y busque el
paquete Microsoft.EntityFrameworkCore.Tools . De esta forma, podrá instalar el paquete o comprobar si ya está
instalado. Como alternativa, vea el enfoque de la CLI si tiene problemas con la PMC.
El comando Add-Migration genera el código para crear el esquema de base de datos inicial. El esquema se basa
en el modelo especificado en DbContext (en el archivo Data/MvcMovieContext.cs). El argumento Initial se usa
para asignar nombre a las migraciones. Puede usar cualquier nombre, pero se suele elegir uno que describa la
migración. Vea Introduction to migrations (Introducción a las migraciones) para obtener más información.
El comando Update-Database ejecuta el método Up en el archivo Migrations/<marca_de_tiempo>_Initial.cs, con
lo que se crea la base de datos.
Puede realizar los pasos anteriores mediante la interfaz de línea de comandos (CLI) en lugar de la PMC:
Agregue las herramientas de EF Core al archivo .csproj.
Ejecute los siguientes comandos desde la consola (en el directorio del proyecto):
Prueba de la aplicación
Ejecute la aplicación y pulse en el vínculo Mvc Movie (Película Mvc).
Pulse Create New (Crear nueva) y cree una película.
Es posible no que pueda escribir comas ni puntos decimales en el campo Price . Para admitir la validación
de jQuery para configuraciones regionales distintas del inglés que usan una coma (",") en lugar de un
punto decimal y formatos de fecha distintos de Estados Unidos, debe seguir unos pasos globalizar la
aplicación. Consulte https://github.com/aspnet/Docs/issues/4076 y recursos adicionales para obtener más
información. Por ahora, tan solo debe escribir números enteros como 10.
En algunas configuraciones regionales debe especificar el formato de fecha. Vea el código que aparece
resaltado a continuación.
using System;
using System.ComponentModel.DataAnnotations;
namespace MvcMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
}
Hablaremos sobre DataAnnotations más adelante en el tutorial.
Al pulsar en Crear el formulario se envía al servidor, donde se guarda la información de la película en una base de
datos. La aplicación redirige a la URL /Movies, donde se muestra la información de la película que acaba de crear.
Cree un par de entradas más de película. Compruebe que todos los vínculos Editar, Detalles y Eliminar son
funcionales.
services.AddDbContext<MvcMovieContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MvcMovieContext")));
}
En el código resaltado anterior se muestra cómo se agrega el contexto de la base de datos de películas al
contenedor inserción de dependencias (en el archivo Startup.cs).
services.AddDbContext<MvcMovieContext>(options => especifica la base de datos que se usará y la cadena de
conexión. => es un operador lambda.
Abra el archivo Controllers/MoviesController.cs y examine el constructor:
El constructor usa la inserción de dependencias para insertar el contexto de base de datos ( MvcMovieContext ) en el
controlador. El contexto de base de datos se usa en cada uno de los métodos CRUD del controlador.
// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
return View(movie);
}
El parámetro id se define como un tipo que acepta valores NULL ( int? ) en caso de que no se proporcione un
valor de identificador.
Se pasa una expresión lambda a SingleOrDefaultAsync para seleccionar entidades de película que coincidan con
los datos de enrutamiento o el valor de consulta de cadena.
Si se encuentra una película, se pasa una instancia del modelo Movie a la vista Details :
return View(movie);
@{
ViewData["Title"] = "Details";
}
<h2>Details</h2>
<div>
<h4>Movie</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Title)
</dt>
<dd>
@Html.DisplayFor(model => model.Title)
</dd>
<dt>
@Html.DisplayNameFor(model => model.ReleaseDate)
</dt>
<dd>
@Html.DisplayFor(model => model.ReleaseDate)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Genre)
</dt>
<dd>
@Html.DisplayFor(model => model.Genre)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Price)
</dt>
<dd>
@Html.DisplayFor(model => model.Price)
</dd>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model.ID">Edit</a> |
<a asp-action="Index">Back to List</a>
</div>
Mediante la inclusión de una instrucción @model en la parte superior del archivo de vista, puede especificar el tipo
de objeto que espera la vista. Cuando se creó el controlador de película, Visual Studio incluyó automáticamente la
siguiente instrucción @model en la parte superior del archivo Details.cshtml:
@model MvcMovie.Models.Movie
Esta directiva @model permite acceder a la película que el controlador pasó a la vista usando un objeto Model
fuertemente tipado. Por ejemplo, en la vista Details.cshtml, el código pasa cada campo de película a las
aplicaciones auxiliares HTML DisplayNameFor y DisplayFor con el objeto Model fuertemente tipado. Los
métodos Create y Edit y las vistas también pasan un objeto de modelo Movie .
Examine la vista Index.cshtml y el método Index en el controlador Movies. Observe cómo el código crea un
objeto List cuando llama al método View . El código pasa esta lista Movies desde el método de acción Index a
la vista:
// GET: Movies
public async Task<IActionResult> Index()
{
return View(await _context.Movie.ToListAsync());
}
Cuando se creó el controlador movies, el scaffolding incluyó automáticamente la siguiente instrucción @model en
la parte superior del archivo Index.cshtml:
@model IEnumerable<MvcMovie.Models.Movie>
Esta directiva @model permite acceder a la lista de películas que el controlador pasó a la vista usando un objeto
Model fuertemente tipado. Por ejemplo, en la vista Index.cshtml, el código recorre en bucle las películas con una
instrucción foreach sobre el objeto Model fuertemente tipado:
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.ID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Como el objeto Model es fuertemente tipado (como un objeto IEnumerable<Movie> ), cada elemento del bucle está
tipado como Movie . Entre otras ventajas, esto significa que se obtiene una comprobación del código en tiempo de
compilación:
Recursos adicionales
Aplicaciones auxiliares de etiquetas
Globalización y localización
A N T E R IO R : A G R E G A R U N A S IG U IE N T E : T R A B A J A R C O N
V IS T A SQL
Trabajar con SQL Server LocalDB en ASP.NET Core
25/06/2018 • 4 minutes to read • Edit Online
"ConnectionStrings": {
"MvcMovieContext": "Server=(localdb)\\mssqllocaldb;Database=MvcMovieContext-
2;Trusted_Connection=True;MultipleActiveResultSets=true"
}
Al implementar la aplicación en un servidor de producción o de prueba, puede usar una variable de entorno u
otro enfoque para establecer la cadena de conexión en una instancia real de SQL Server. Para más información,
vea Configuración.
Haga clic con el botón derecho en la tabla Movie > Diseñador de vistas.
Observe el icono de llave junto a ID . De forma predeterminada, EF convierte una propiedad denominada ID en
la clave principal.
Haga clic con el botón derecho en la tabla Movie > Ver datos
Inicialización de la base de datos
Cree una nueva clase denominada SeedData en la carpeta Models. Reemplace el código generado con el
siguiente:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;
namespace MvcMovie.Models
{
public static class SeedData
{
public static void Initialize(IServiceProvider serviceProvider)
{
using (var context = new MvcMovieContext(
serviceProvider.GetRequiredService<DbContextOptions<MvcMovieContext>>()))
{
// Look for any movies.
if (context.Movie.Any())
{
return; // DB has been seeded
}
context.Movie.AddRange(
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-1-11"),
Genre = "Romantic Comedy",
Price = 7.99M
},
new Movie
{
Title = "Ghostbusters ",
ReleaseDate = DateTime.Parse("1984-3-13"),
Genre = "Comedy",
Price = 8.99M
},
new Movie
{
Title = "Ghostbusters 2",
ReleaseDate = DateTime.Parse("1986-2-23"),
Genre = "Comedy",
Price = 9.99M
},
new Movie
{
Title = "Rio Bravo",
ReleaseDate = DateTime.Parse("1959-4-15"),
Genre = "Western",
Price = 3.99M
}
);
context.SaveChanges();
}
}
}
}
Si hay alguna película en la base de datos, se devuelve el inicializador y no se agrega ninguna película.
if (context.Movie.Any())
{
return; // DB has been seeded.
}
Agregar el inicializador
[!code-csharp]
ASP.NET Core 2.x
ASP.NET Core 1.x
Agregue el inicializador al método Main en el archivo Program.cs:
[!code-csharp]
Prueba de la aplicación
Elimine todos los registros de la base de datos. Puede hacerlo con los vínculos de eliminación en el
explorador o desde SSOX.
Obligue a la aplicación a inicializarse (llame a los métodos de la clase Startup ) para que se ejecute el
método de inicialización. Para forzar la inicialización, se debe detener y reiniciar IIS Express. Puede hacerlo
con cualquiera de los siguientes enfoques:
Haga clic con el botón derecho en el icono Bandeja del sistema de IIS Express del área de
notificación y pulse en Salir o en Detener sitio.
Abra el archivo Models/Movie.cs y agregue las líneas resaltadas que se muestran a continuación:
[!code-csharp]
[!code-csharp]
En el próximo tutorial hablaremos de DataAnnotations. El atributo Display especifica qué se muestra como
nombre de un campo (en este caso, "Release Date" en lugar de "ReleaseDate"). El atributo DataType especifica el
tipo de los datos (Date), así que la información de hora almacenada en el campo no se muestra.
Vaya al controlador Movies y mantenga el puntero del mouse sobre un vínculo Edit (Editar) para ver la dirección
URL de destino.
Los vínculos Edit (Editar), Details (Detalles) y Delete (Eliminar) se generan mediante la aplicación auxiliar de
etiquetas de delimitador de MVC Core en el archivo Views/Movies/Index.cshtml.
Las aplicaciones auxiliares de etiquetas permiten que el código de servidor participe en la creación y la
representación de elementos HTML en archivos de Razor. En el código anterior, AnchorTagHelper genera
dinámicamente el valor del atributo HTML href a partir del identificador de ruta y el método de acción del
controlador. Use Ver código fuente en su explorador preferido o use las herramientas de desarrollo para
examinar el marcado generado. A continuación se muestra una parte del HTML generado:
<td>
<a href="/Movies/Edit/4"> Edit </a> |
<a href="/Movies/Details/4"> Details </a> |
<a href="/Movies/Delete/4"> Delete </a>
</td>
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
ASP.NET Core traduce http://localhost:1234/Movies/Edit/4 en una solicitud al método de acción Edit del
controlador Movies con el parámetro Id de 4. (Los métodos de controlador también se denominan métodos de
acción).
Las aplicaciones auxiliares de etiquetas son una de las nuevas características más populares de ASP.NET Core.
Para más información, vea Recursos adicionales.
Abra el controlador Movies y examine los dos métodos de acción Edit . En el código siguiente se muestra el
método HTTP GET Edit , que captura la película y rellena el formulario de edición generado por el archivo de Razor
Edit.cshtml.
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
En el código siguiente se muestra el método HTTP POST Edit , que procesa los valores de película publicados:
// POST: Movies/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
if (id != movie.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction("Index");
}
return View(movie);
}
El atributo [Bind] es una manera de proteger contra el exceso de publicación. Solo debe incluir propiedades en el
atributo [Bind] que quiera cambiar. Para más información, vea Protección del controlador frente al exceso de
publicación. ViewModels ofrece un enfoque alternativo para evitar el exceso de publicaciones.
Observe que el segundo método de acción Edit va precedido del atributo [HttpPost] .
// POST: Movies/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
if (id != movie.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction("Index");
}
return View(movie);
}
El atributo HttpPost especifica que este método Edit se puede invocar solamente para solicitudes POST . Podría
aplicar el atributo [HttpGet] al primer método de edición, pero no es necesario hacerlo porque [HttpGet] es el
valor predeterminado.
El atributo ValidateAntiForgeryToken se usa para impedir la falsificación de una solicitud y se empareja con un
token antifalsificación generado en el archivo de vista de edición (Views/Movies/Edit.cshtml). El archivo de vista de
edición genera el token antifalsificación con la aplicación auxiliar de etiquetas de formulario.
<form asp-action="Edit">
La aplicación auxiliar de etiquetas de formulario genera un token antifalsificación oculto que debe coincidir con el
token antifalsificación generado por [ValidateAntiForgeryToken] en el método Edit del controlador Movies. Para
más información, vea Prevención de ataques de falsificación de solicitudes.
El método HttpGet Edit toma el parámetro ID de la película, busca la película con el método
SingleOrDefaultAsync de Entity Framework y devuelve la película seleccionada a la vista de edición. Si no se
encuentra una película, se devuelve NotFound (HTTP 404).
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
Cuando el sistema de scaffolding creó la vista de edición, examinó la clase Movie y creó código para representar
los elementos <label> y <input> para cada propiedad de la clase. En el ejemplo siguiente se muestra la vista de
edición que generó el sistema de scaffolding de Visual Studio:
@model MvcMovie.Models.Movie
@{
ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<form asp-action="Edit">
<div class="form-horizontal">
<h4>Movie</h4>
<hr />
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="ID" />
<div class="form-group">
<label asp-for="Title" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="ReleaseDate" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="ReleaseDate" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="Genre" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Genre" class="form-control" />
<span asp-validation-for="Genre" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="Price" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
</form>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Observe cómo la plantilla de vista tiene una instrucción @model MvcMovie.Models.Movie en la parte superior del
archivo. @model MvcMovie.Models.Movie especifica que la vista espera que el modelo de la plantilla de vista sea del
tipo Movie .
El código con scaffolding usa varios métodos de aplicación auxiliar de etiquetas para simplificar el marcado
HTML. La aplicación auxiliar de etiquetas muestra el nombre del campo: "Title" (Título), "ReleaseDate" (Fecha de
lanzamiento), "Genre" (Género) o "Price" (Precio). La aplicación auxiliar de etiquetas de entrada representa un
elemento HTML <input> . La aplicación auxiliar de etiquetas de validación muestra cualquier mensaje de
validación asociado a esa propiedad.
Ejecute la aplicación y navegue a la URL /Movies . Haga clic en un vínculo Edit (Editar). En el explorador, vea el
código fuente de la página. El código HTML generado para el elemento <form> se muestra abajo.
Los elementos <input> se muestran en un elemento HTML <form> cuyo atributo action se establece para
publicar en la dirección URL /Movies/Edit/id . Los datos del formulario se publicarán en el servidor cuando se
haga clic en el botón Save . La última línea antes del cierre del elemento </form> muestra el token XSRF oculto
generado por la aplicación auxiliar de etiquetas de formulario.
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction("Index");
}
return View(movie);
}
El atributo [ValidateAntiForgeryToken] valida el token XSRF oculto generado por el generador de tokens
antifalsificación en la herramienta auxiliar de etiquetas de formulario.
El sistema de enlace de modelos toma los valores de formulario publicados y crea un objeto Movie que se pasa
como el parámetro movie . El método ModelState.IsValid comprueba que los datos presentados en el formulario
pueden usarse para modificar (editar o actualizar) un objeto Movie . Si los datos son válidos, se guardan. Los
datos de película actualizados (o modificados) se guardan en la base de datos mediante una llamada al método
SaveChangesAsync del contexto de base de datos. Después de guardar los datos, el código redirige al usuario al
método de acción Index de la clase MoviesController , que muestra la colección de películas, incluidos los
cambios que se acaban de hacer.
Antes de que el formulario se envíe al servidor, la validación del lado cliente comprueba cualquier regla de
validación en los campos. Si hay errores de validación, se muestra un mensaje de error y no se publica el
formulario. Si JavaScript está deshabilitado, no dispondrá de la validación del lado cliente, sino que el servidor
detectará los valores publicados que no son válidos y los valores de formulario se volverán a mostrar con
mensajes de error. Más adelante en el tutorial se examina la validación de modelos con más detalle. La aplicación
auxiliar de etiquetas de validación en la plantilla de vista Views/Movies/Edit.cshtml se encarga de mostrar los
mensajes de error correspondientes.
Todos los métodos HttpGet del controlador de películas siguen un patrón similar. Obtienen un objeto de película
(o una lista de objetos, en el caso de Index ) y pasan el objeto (modelo) a la vista. El método Create pasa un
objeto de película vacío a la vista Create . Todos los métodos que crean, editan, eliminan o modifican los datos lo
hacen en la sobrecarga [HttpPost] del método. La modificación de datos en un método HTTP GET supone un
riesgo de seguridad. La modificación de datos en un método HTTP GET también infringe procedimientos
recomendados de HTTP y el patrón de arquitectura REST, que especifica que las solicitudes GET no deben
cambiar el estado de la aplicación. En otras palabras, realizar una operación GET debería ser una operación segura
sin efectos secundarios, que no modifica los datos persistentes.
Recursos adicionales
Globalización y localización
Introducción a las aplicaciones auxiliares de etiquetas
Creación de aplicaciones auxiliares de etiquetas
Prevención de ataques de falsificación de solicitudes
Protección del controlador frente al exceso de publicación
ViewModels
Aplicación auxiliar de etiquetas de formulario
Aplicación auxiliar de etiquetas de entrada
Aplicación auxiliar de etiquetas de elementos de etiqueta
Aplicación auxiliar de etiquetas de selección
Aplicación auxiliar de etiquetas de validación
A N T E R IO R S IG U IE N T E
Adición de una búsqueda en una aplicación de
ASP.NET Core MVC
25/06/2018 • 12 minutes to read • Edit Online
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
La primera línea del método de acción Index crea una consulta LINQ para seleccionar las películas:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
El código s => s.Title.Contains() anterior es una expresión Lambda. Las lambdas se usan en consultas LINQ
basadas en métodos como argumentos para métodos de operador de consulta estándar, tales como el método
Where o Contains (usado en el código anterior). Las consultas LINQ no se ejecutan cuando se definen ni cuando
se modifican mediante una llamada a un método, como Where , Contains u OrderBy . En su lugar, se aplaza la
ejecución de la consulta. Esto significa que la evaluación de una expresión se aplaza hasta que su valor realizado se
repita realmente o se llame al método ToListAsync . Para más información sobre la ejecución de consultas en
diferido, vea Ejecución de la consulta.
Nota: El método Contains se ejecuta en la base de datos, no en el código de c# que se muestra arriba. La
distinción entre mayúsculas y minúsculas en la consulta depende de la base de datos y la intercalación. En SQL
Server, Contains se asigna a SQL LIKE, que distingue entre mayúsculas y minúsculas. En SQLite, con la
intercalación predeterminada, se distingue entre mayúsculas y minúsculas.
Navegue a /Movies/Index . Anexe una cadena de consulta como ?searchString=Ghost a la dirección URL. Se
muestran las películas filtradas.
Si se cambia la firma del método Index para que tenga un parámetro con el nombre id , el parámetro id
coincidirá con el marcador {id} opcional para el conjunto de rutas predeterminado en Startup.cs.
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
Puede cambiar fácilmente el nombre del parámetro searchString por id con el comando rename. Haga clic con
el botón derecho en searchString > Cambiar nombre.
Se resaltarán los objetivos del cambio de nombre.
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (!String.IsNullOrEmpty(id))
{
movies = movies.Where(s => s.Title.Contains(id));
}
Ahora puede pasar el título de la búsqueda como datos de ruta (un segmento de dirección URL ) en lugar de como
un valor de cadena de consulta.
Sin embargo, no se puede esperar que los usuarios modifiquen la dirección URL cada vez que quieran buscar una
película. Por tanto, ahora deberá agregar elementos de la interfaz de usuario con los que podrán filtrar las
películas. Si cambió la firma del método Index para probar cómo pasar el parámetro ID enlazado a una ruta,
vuelva a cambiarlo para que tome un parámetro denominado searchString :
public async Task<IActionResult> Index(string searchString)
{
var movies = from m in _context.Movie
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
La etiqueta HTML <form> usa la aplicación auxiliar de etiquetas de formulario, por lo que cuando se envía el
formulario, la cadena de filtro se registra en la acción Index del controlador de películas. Guarde los cambios y
después pruebe el filtro.
No hay ninguna sobrecarga [HttpPost] del método Index como cabría esperar. No es necesario, porque el
método no cambia el estado de la aplicación, simplemente filtra los datos.
Después, puede agregar el método [HttpPost] Index siguiente.
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
El parámetro notUsed se usa para crear una sobrecarga para el método Index . Hablaremos sobre esto más
adelante en el tutorial.
Si agrega este método, el invocador de acción coincidiría con el método [HttpPost] Index , mientras que el
método [HttpPost] Index se ejecutaría tal como se muestra en la imagen de abajo.
Sin embargo, aunque agregue esta versión de [HttpPost] al método Index , hay una limitación en cómo se ha
implementado todo esto. Supongamos que quiere marcar una búsqueda en particular o que quiere enviar un
vínculo a sus amigos donde puedan hacer clic para ver la misma lista filtrada de películas. Tenga en cuenta que la
dirección URL de la solicitud HTTP POST es la misma que la dirección URL de la solicitud GET
(localhost:xxxxx/Movies/Index): no hay información de búsqueda en la URL. La información de la cadena de
búsqueda se envía al servidor como un valor de campo de formulario. Puede comprobarlo con las herramientas
de desarrollo del explorador o con la excelente herramienta Fiddler. En la imagen de abajo se muestran las
herramientas de desarrollo del explorador Chrome:
Puede ver el parámetro de búsqueda y el token XSRF en el cuerpo de la solicitud. Tenga en cuenta, como se
mencionó en el tutorial anterior, que la aplicación auxiliar de etiquetas de formulario genera un token XSRF
antifalsificación. Como no se van a modificar datos, no es necesario validar el token con el método del controlador.
El parámetro de búsqueda se encuentra en el cuerpo de solicitud y no en la dirección URL. Por eso no se puede
capturar dicha información para marcarla o compartirla con otros usuarios. Para solucionar este problema,
especificaremos que la solicitud sea HTTP GET .
Observe cómo IntelliSense le ayuda a actualizar el marcado.
Observe que la fuente es diferente en la etiqueta <form> . Esta fuente distinta indica que la etiqueta es compatible
con las aplicaciones auxiliares de etiquetas.
Ahora, cuando se envía una búsqueda, la URL contiene la cadena de consulta de búsqueda. La búsqueda también
será dirigida al método de acción HttpGet Index , aunque tenga un método HttpPost Index .
namespace MvcMovie.Models
{
public class MovieGenreViewModel
{
public List<Movie> movies;
public SelectList genres;
public string movieGenre { get; set; }
}
}
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (!String.IsNullOrEmpty(movieGenre))
{
movies = movies.Where(x => x.Genre == movieGenre);
}
return View(movieGenreVM);
}
El código siguiente es una consulta LINQ que recupera todos los géneros de la base de datos.
La SelectList de géneros se crea mediante la proyección de los distintos géneros (no queremos que nuestra lista
de selección tenga géneros duplicados).
movieGenreVM.genres = new SelectList(await genreQuery.Distinct().ToListAsync())
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.movies[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.movies[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.movies[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.movies[0].Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.movies)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.ID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Examine la expresión lambda usada en la siguiente aplicación auxiliar HTML:
@Html.DisplayNameFor(model => model.movies[0].Title)
En el código anterior, la aplicación auxiliar HTML DisplayNameFor inspecciona la propiedad Title a la que se
hace referencia en la expresión lambda para determinar el nombre para mostrar. Puesto que la expresión lambda
se inspecciona en lugar de evaluarse, no recibirá una infracción de acceso cuando model , model.movies o
model.movies[0] sean null o estén vacíos. Cuando se evalúa la expresión lambda (por ejemplo,
@Html.DisplayFor(modelItem => item.Title) ), se evalúan los valores de propiedad del modelo.
A N T E R IO R S IG U IE N T E
Agregar un nuevo campo a una aplicación ASP.NET
Core
25/06/2018 • 7 minutes to read • Edit Online
[Bind("ID,Title,ReleaseDate,Genre,Price,Rating")]
También debe actualizar las plantillas de vista para mostrar, crear y editar la nueva propiedad Rating en la vista
del explorador.
Edite el archivo /Views/Movies/Index.cshtml y agregue un campo Rating :
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.movies[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.movies[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.movies[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.movies[0].Price)
</th>
<th>
@Html.DisplayNameFor(model => model.movies[0].Rating)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.movies)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.Rating)
</td>
<td>
Actualice /Views/Movies/Create.cshtml con un campo Rating . Puede copiar o pegar el elemento "form group"
anterior y permitir que IntelliSense le ayude a actualizar los campos. IntelliSense funciona con aplicaciones
auxiliares de etiquetas. Nota: En la versión RTM de Visual Studio 2017 debe instalar Servicios de lenguaje Razor
para Razor IntelliSense. Esto se resolverá en la próxima versión.
La aplicación no funcionará hasta que la base de datos se actualice para incluir el nuevo campo. Si la ejecuta
ahora, se producirá la siguiente SqlException :
SqlException: Invalid column name 'Rating'.
Este error aparece porque la clase del modelo Movie actualizado es diferente al esquema de la tabla Movie de la
base de datos existente. (No hay ninguna columna Rating en la tabla de la base de datos).
Este error se puede resolver de varias maneras:
1. Haga que Entity Framework quite de forma automática la base de datos y la vuelva a crear basándose en el
nuevo esquema de la clase del modelo. Este enfoque resulta muy conveniente al principio del ciclo de
desarrollo cuando se está realizando el desarrollo activo en una base de datos de prueba; permite
desarrollar rápidamente el esquema del modelo y la base de datos juntos. La desventaja es que se pierden
los datos existentes en la base de datos, así que no use este enfoque en una base de datos de producción.
Usar un inicializador para inicializar automáticamente una base de datos con datos de prueba suele ser una
manera productiva de desarrollar una aplicación.
2. Modifique explícitamente el esquema de la base de datos existente para que coincida con las clases del
modelo. La ventaja de este enfoque es que se conservan los datos. Puede realizar este cambio de forma
manual o mediante la creación de un script de cambio de base de datos.
3. Use Migraciones de Code First para actualizar el esquema de la base de datos.
Para este tutorial se usa Migraciones de Code First.
Actualice la clase SeedData para que proporcione un valor para la nueva columna. A continuación se muestra un
cambio de ejemplo, aunque es conveniente realizarlo con cada new Movie .
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-1-11"),
Genre = "Romantic Comedy",
Rating = "R",
Price = 7.99M
},
Compile la solución.
En el menú Herramientas, seleccione Administrador de paquetes NuGet > Consola del Administrador de
paquetes.
En PCM, escriba los siguientes comandos:
Add-Migration Rating
Update-Database
El comando Add-Migration indica el marco de trabajo de migración para examinar el modelo Movie actual con el
esquema de base de datos Movie actual y para crear el código con el que se migrará la base de datos al nuevo
modelo. El nombre "Rating" es arbitrario y se usa para asignar nombre al archivo de migración. Resulta útil
emplear un nombre descriptivo para el archivo de migración.
Si elimina todos los registros de la base de datos, el inicializador inicializa la base de datos e incluye el campo
Rating . Puede hacerlo con los vínculos de eliminación en el explorador o desde SSOX.
Ejecute la aplicación y compruebe que puede crear, editar o mostrar películas con un campo Rating . También
debe agregar el campo Rating a las plantillas de vista Edit , Details y Delete .
A N T E R IO R S IG U IE N T E
Adición de validación
25/06/2018 • 17 minutes to read • Edit Online
Actualice la clase Movie para aprovechar los atributos de validación integrados Required , StringLength ,
RegularExpression y Range .
[!code-csharp]
[!code-csharp]
Los atributos de validación especifican el comportamiento que quiere aplicar en las propiedades del modelo al
que se aplican. Los atributos Required y MinimumLength indican que una propiedad debe tener un valor, pero
nada evita que un usuario escriba espacios en blanco para satisfacer esta validación. El atributo
RegularExpression se usa para limitar los caracteres que se pueden escribir. En el código anterior, Genre y
Rating solamente pueden usar letras (no se permiten espacios en blanco, números ni caracteres especiales). El
atributo Range restringe un valor a un intervalo determinado. El atributo StringLength permite establecer la
longitud máxima de una propiedad de cadena y, opcionalmente, su longitud mínima. Los tipos de valor (como
decimal , int , float , DateTime ) son intrínsecamente necesarios y no necesitan el atributo [Required] .
Cuando ASP.NET aplica automáticamente reglas de validación, logramos que la aplicación sea más sólida.
También nos permite asegurarnos de que todo se valida y que no nos dejamos ningún dato incorrecto en la base
de datos accidentalmente.
Observe cómo el formulario presenta automáticamente un mensaje de error de validación adecuado en cada
campo que contiene un valor no válido. Los errores se aplican en el lado cliente (con JavaScript y jQuery) y en el
lado servidor (cuando un usuario tiene JavaScript deshabilitado).
Una ventaja importante es que no fue necesario cambiar ni una sola línea de código en la clase MoviesController
o en la vista Create.cshtml para habilitar esta interfaz de usuario de validación. El controlador y las vistas que creó
en pasos anteriores de este tutorial seleccionaron automáticamente las reglas de validación que especificó
mediante atributos de validación en las propiedades de la clase del modelo Movie . Pruebe la aplicación mediante
el método de acción Edit y se aplicará la misma validación.
Los datos del formulario no se enviarán al servidor hasta que dejen de producirse errores de validación de cliente.
Puede comprobarlo colocando un punto de interrupción en el método HTTP Post mediante la herramienta
Fiddler o las herramientas de desarrollo F12.
// GET: Movies/Create
public IActionResult Create()
{
return View();
}
// POST: Movies/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
[Bind("ID,Title,ReleaseDate,Genre,Price, Rating")] Movie movie)
{
if (ModelState.IsValid)
{
_context.Add(movie);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(movie);
}
El primer método de acción Create (HTTP GET) muestra el formulario de creación inicial. La segunda versión (
[HttpPost] ) controla el envío de formulario. El segundo método Create (la versión [HttpPost] ) llama a
ModelState.IsValid para comprobar si la película tiene errores de validación. Al llamar a este método se evalúan
todos los atributos de validación que se hayan aplicado al objeto. Si el objeto tiene errores de validación, el
método Create vuelve a mostrar el formulario. Si no hay ningún error, el método guarda la nueva película en la
base de datos. En nuestro ejemplo de película, el formulario no se publica en el servidor si se detectan errores de
validación del lado cliente; cuando hay errores de validación en el lado cliente, no se llama nunca al segundo
método Create . Si deshabilita JavaScript en el explorador, se deshabilita también la validación del cliente y puede
probar si el método Create HTTP POST ModelState.IsValid detecta errores de validación.
Puede establecer un punto de interrupción en el método [HttpPost] Create y comprobar si nunca se llama al
método. La validación del lado cliente no enviará los datos del formulario si se detectan errores de validación. Si
deshabilita JavaScript en el explorador y después envía el formulario con errores, se alcanzará el punto de
interrupción. Puede seguir obteniendo validación completa sin JavaScript.
En la siguiente imagen se muestra cómo deshabilitar JavaScript en el explorador Firefox.
En la siguiente imagen se muestra cómo deshabilitar JavaScript en el explorador Chrome.
Después de deshabilitar JavaScript, publique los datos no válidos y siga los pasos del depurador.
Abajo se muestra una parte de la plantilla de vista Create.cshtml a la que se aplicó scaffolding en un paso anterior
de este tutorial. Los métodos de acción que se muestran arriba la usan para mostrar el formulario inicial y para
volver a mostrarlo en caso de error.
<form asp-action="Create">
<div class="form-horizontal">
<h4>Movie</h4>
<hr />
La aplicación auxiliar de etiquetas de entrada usa los atributos DataAnnotations y genera los atributos HTML
necesarios para la validación de jQuery en el lado cliente. La aplicación auxiliar de etiquetas de validación muestra
errores de validación. Para más información, vea Introduction to model validation in ASP.NET Core MVC
(Introducción a la validación de modelos en ASP.NET Core MVC ).
Lo realmente bueno de este enfoque es que ni el controlador ni la plantilla de vista Create saben que las reglas
de validación actuales se están aplicando ni conocen los mensajes de error específicos que se muestran. Las reglas
de validación y las cadenas de error solo se especifican en la clase Movie . Estas mismas reglas de validación se
aplican automáticamente a la vista Edit y a cualquier otra vista de plantillas creada que edite el modelo.
Cuando necesite cambiar la lógica de validación, puede hacerlo exactamente en un solo lugar mediante la adición
de atributos de validación al modelo (en este ejemplo, la clase Movie ). No tendrá que preocuparse de que
diferentes partes de la aplicación sean incoherentes con el modo en que se aplican las reglas: toda la lógica de
validación se definirá en un solo lugar y se usará en todas partes. Esto mantiene el código muy limpio y hace que
sea fácil de mantener y evolucionar. También significa que respeta totalmente el principio DRY.
[Range(1, 100)]
[DataType(DataType.Currency)]
public decimal Price { get; set; }
Los atributos DataType solo proporcionan sugerencias para que el motor de vista aplique formato a los datos (y
ofrece atributos o elementos como <a> para las direcciones URL y <a href="mailto:EmailAddress.com"> para el
correo electrónico). Use el atributo RegularExpression para validar el formato de los datos. El atributo DataType
no es un atributo de validación, sino que se usa para especificar un tipo de datos más específico que el tipo
intrínseco de la base de datos. En este caso solo queremos realizar un seguimiento de la fecha, no la hora. La
enumeración DataType proporciona muchos tipos de datos, como Date (Fecha), Time (Hora), PhoneNumber
(Número de teléfono), Currency (Moneda), EmailAddress (Dirección de correo electrónico), etc. El atributo
DataType también puede permitir que la aplicación proporcione automáticamente características específicas del
tipo. Por ejemplo, se puede crear un vínculo mailto: para DataType.EmailAddress y se puede proporcionar un
selector de datos para DataType.Date en exploradores compatibles con HTML5. Los atributos DataType emiten
atributos HTML 5 data- (se pronuncia "datos dash") que los exploradores HTML 5 pueden comprender. Los
atributos DataType no proporcionan ninguna validación.
DataType.Date no especifica el formato de la fecha que se muestra. De manera predeterminada, el campo de
datos se muestra según los formatos predeterminados basados en el elemento CultureInfo del servidor.
El atributo DisplayFormat se usa para especificar el formato de fecha de forma explícita:
El valor ApplyFormatInEditMode especifica que el formato se debe aplicar también cuando el valor se muestra en
un cuadro de texto para su edición. En algunos campos este comportamiento puede no ser conveniente. Por
poner un ejemplo, es probable que con valores de moneda no se quiera que el símbolo de la divisa se incluya en
el cuadro de texto editable.
El atributo DisplayFormat puede usarse por sí solo, pero normalmente se recomienda usar el atributo DataType .
El atributo DataType transmite la semántica de los datos en contraposición a cómo se representa en una pantalla
y ofrece las siguientes ventajas que no proporciona DisplayFormat:
El explorador puede habilitar características de HTML5 (por ejemplo, mostrar un control de calendario, el
símbolo de moneda adecuado según la configuración regional, vínculos de correo electrónico, etc.).
De manera predeterminada, el explorador representa los datos con el formato correcto según la
configuración regional.
El atributo DataType puede habilitar MVC para que elija la plantilla de campo adecuada para representar
los datos ( DisplayFormat , si se usa por sí solo, usa la plantilla de cadena).
NOTE
La validación de jQuery no funciona con el atributo Range ni DateTime . Por ejemplo, el código siguiente siempre muestra
un error de validación del lado cliente, incluso cuando la fecha está en el intervalo especificado:
[Range(typeof(DateTime), "1/1/1966", "1/1/2020")]
Debe deshabilitar la validación de fechas de jQuery para usar el atributo Range con DateTime . Por lo general no
se recomienda compilar fechas fijas en los modelos, así que desaconseja usar el atributo Range y DateTime .
El código siguiente muestra la combinación de atributos en una línea:
[!code-csharp]
[!code-csharp]
En la siguiente parte de la serie de tutoriales, revisaremos la aplicación y realizaremos algunas mejoras a los
métodos Details y Delete generados automáticamente.
Recursos adicionales
Trabajar con formularios
Globalización y localización
Introducción a las aplicaciones auxiliares de etiquetas
Creación de aplicaciones auxiliares de etiquetas
A N T E R IO R S IG U IE N T E
Examinar los métodos Details y Delete de una
aplicación ASP.NET Core
25/06/2018 • 5 minutes to read • Edit Online
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
// GET: Movies/Delete/5
public async Task<IActionResult> Delete(int? id)
{
// POST: Movies/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
Common Language Runtime (CLR ) requiere métodos sobrecargados para disponer de una firma de parámetro
única (mismo nombre de método, pero lista de parámetros diferente). En cambio, aquí necesita dos métodos
Delete (uno para GET y otro para POST ) que tienen la misma firma de parámetro (ambos deben aceptar un
número entero como parámetro).
Hay dos enfoques para este problema. Uno consiste en proporcionar nombres diferentes a los métodos, que es lo
que hizo el mecanismo de scaffolding en el ejemplo anterior. Pero esto implica un pequeño problema: ASP.NET
asigna segmentos de una dirección URL a los métodos de acción por nombre y, si cambia el nombre de un método,
normalmente el enrutamiento no podría encontrar ese método. La solución es la que ve en el ejemplo, que consiste
en agregar el atributo ActionName("Delete") al método DeleteConfirmed . Ese atributo efectúa la asignación para el
sistema de enrutamiento para que una dirección URL que incluya /Delete/ para una solicitud POST busque el
método DeleteConfirmed .
Otra solución alternativa común para los métodos que tienen nombres y firmas idénticos consiste en cambiar la
firma del método POST artificialmente para incluir un parámetro adicional (sin usar). Es lo que hicimos en una
publicación anterior, cuando agregamos el parámetro notUsed . Podría hacer lo mismo aquí para el método
[HttpPost] Delete :
// POST: Movies/Delete/6
[ValidateAntiForgeryToken]
public async Task<IActionResult> Delete(int id, bool notUsed)
Publicar en Azure
Vea Publicar una aplicación web de ASP.NET Core en Azure App Service con Visual Studio para obtener
instrucciones sobre cómo publicar esta aplicación en Azure con Visual Studio. También se puede publicar la
aplicación desde la línea de comandos.
A N T E R IO R
Compilación de API web con ASP.NET Core
21/06/2018 • 6 minutes to read • Edit Online
[!code-csharp]
En las secciones siguientes se describen las ventajas de las características que aporta el atributo.
Respuestas HTTP 400 automáticas
Los errores de validación desencadenan automáticamente una respuesta HTTP 400. El código siguiente deja de ser
necesario en las acciones:
[!code-csharp]
Este comportamiento predeterminado se deshabilita con siguiente código en Startup.ConfigureServices:
[!code-csharp]
Inferencia de parámetro de origen de enlace
Un atributo de origen de enlace define la ubicación del valor del parámetro de una acción. Existen los atributos de
origen de enlace siguientes:
ATRIBUTO ORIGEN DE ENLACE
NOTE
No use [FromRoute] si los valores pueden contener %2f (es decir, / ) porque %2f no incluirá el carácter sin escape / .
Use [FromQuery] si el valor puede contener %2f .
Sin el [ApiController] atributo, los atributos de origen de enlace se definen explícitamente. En el ejemplo
siguiente, el atributo [FromQuery] indica que el valor del parámetro discontinuedOnly se proporciona en la cadena
de consulta de la dirección URL de la solicitud:
[!code-csharp]
Las reglas de inferencia se aplican para los orígenes de datos predeterminados de los parámetros de acción. Estas
reglas configuran los orígenes de enlace que aplicaría manualmente a los parámetros de acción. Los atributos de
origen de enlace presentan este comportamiento:
[FromBody] se infiere para los parámetros de tipo complejo. La excepción a esta regla es cualquier tipo
integrado complejo que tenga un significado especial, como IFormCollection o CancellationToken. El código de
inferencia del origen de enlace omite esos tipos especiales. Cuando una acción contiene más de un parámetro
que se especifica explícitamente (a través de [FromBody] ) o se infiere como enlazado desde el cuerpo de la
solicitud, se produce una excepción. Por ejemplo, las firmas de acción siguientes provocan una excepción:
[!code-csharp]
[FromForm ] se infiere para los parámetros de acción de tipo IFormFile o IFormFileCollection. No se infiere
para los tipos simples o definidos por el usuario.
[FromRoute] se infiere para cualquier nombre de parámetro de acción que coincida con un parámetro de la
plantilla de ruta. Si varias rutas coinciden con un parámetro de acción, cualquier valor de ruta se considera
[FromRoute] .
[FromQuery] se infiere para cualquier otro parámetro de acción.
Recursos adicionales
Tipos de valor devuelto de acción del controlador
Formateadores personalizados
Aplicación de formato a datos de respuesta
Páginas de ayuda mediante Swagger
Enrutamiento a acciones del controlador
Crear una API web con ASP.NET Core y Visual Studio
Code
17/05/2018 • 25 minutes to read • Edit Online
Información general
En este tutorial se crea la siguiente API:
GET /api/todo Obtener todas las tareas Ninguna Matriz de tareas pendientes
pendientes
El cliente es todo lo que consume la API web (aplicación móvil, explorador, etcétera). En este tutorial no se
crea ningún cliente. Postman o curl se utilizan como el cliente para probar la aplicación.
Un modelo es un objeto que representa los datos de la aplicación. En este caso, el único modelo es una tarea
pendiente. Los modelos se representan como clases de C#, también conocidas como clases POCO (del
inglés Plain Old C# Object, objetos CRL antiguos sin formato).
Un controlador es un objeto que controla solicitudes HTTP y crea la respuesta HTTP. Esta aplicación tiene un
único controlador.
Para simplificar el tutorial, la aplicación no usa una base de datos persistente. La aplicación de ejemplo
almacena tareas pendientes en una base de datos en memoria.
Requisitos previos
Install the following:
.NET Core SDK 2.0 or later
Visual Studio Code
C# for Visual Studio Code
.NET Core 2.1 SDK or later
Visual Studio Code
C# for Visual Studio Code
Crear el proyecto
Desde una consola, ejecute los siguientes comandos:
La carpeta TodoApi se abre en Visual Studio Code (VS Code). Seleccione el archivo Startup.cs.
Seleccione Sí en el mensaje de advertencia "Required assets to build and debug are missing from 'TodoApi'.
Add them?" (Faltan los activos necesarios para compilar y depurar en 'TodoApi'. ¿Quiere agregarlos?).
Seleccione Restaurar en el mensaje de información "There are unresolved dependencies" (Hay dependencias
no resueltas).
Presione Depurar (F5) para compilar y ejecutar el programa. En un navegador, vaya a
http://localhost:5000/api/values. Se muestra el siguiente resultado:
["value1","value2"]
Vea Ayuda de Visual Studio Code para obtener sugerencias sobre el uso de VS Code.
using Microsoft.EntityFrameworkCore;
namespace TodoApi.Models
{
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}
Adición de un controlador
En la carpeta Controladores, cree una clase denominada TodoController . Reemplace el contenido por el siguiente
código:
[!code-csharp]
El código anterior define una clase de controlador de API sin métodos. En las secciones siguientes, se agregan
métodos para implementar la API.
[!code-csharp]
El código anterior define una clase de controlador de API sin métodos. En las secciones siguientes, se agregan
métodos para implementar la API. La clase se anota con un atributo [ApiController] para habilitar algunas
características muy prácticas. Para más información sobre las características que el atributo habilita, vea Anotación
de una clase con ApiControllerAttribute.
El constructor del controlador usa la inserción de dependencias para insertar el contexto de base de datos (
TodoContext ) en el controlador. El contexto de base de datos se usa en cada uno de los métodos CRUD del
controlador. El constructor agrega un elemento a la base de datos en memoria si no existe ninguno.
Tareas pendientes
Para obtener tareas pendientes, agregue estos métodos a la clase TodoController :
[!code-csharp]
[!code-csharp]
Estos métodos implementan los dos métodos GET:
GET /api/todo
GET /api/todo/{id}
[
{
"id": 1,
"name": "Item1",
"isComplete": false
}
]
Más adelante en el tutorial, veremos cómo se puede ver la respuesta HTTP por medio de Postman o curl.
Enrutamiento y rutas URL
El atributo [HttpGet] indica un método que responde a una solicitud HTTP GET. La ruta de dirección URL para
cada método se construye como sigue:
Tome la cadena de plantilla en el atributo Route del controlador:
[!code-csharp]
[!code-csharp]
Reemplace [controller] por el nombre del controlador, que es el nombre de clase de controlador sin el sufijo
"Controller". En este ejemplo, el nombre de clase de controlador es TodoController y el nombre de raíz es
"todo". El enrutamiento en ASP.NET Core no distingue entre mayúsculas y minúsculas.
Si el atributo [HttpGet] tiene una plantilla de ruta (como [HttpGet("/products")] ), anexiónela a la ruta de
acceso. En este ejemplo no se usa una plantilla. Para más información, vea Enrutamiento mediante atributos con
atributos Http[Verb].
En el siguiente método GetById , "{id}" es una variable de marcador de posición correspondiente al identificador
único de la tarea pendiente. Cuando GetById se invoca, asigna el valor "{id}" de la dirección URL al parámetro
id del método.
[!code-csharp]
[!code-csharp]
Name = "GetTodo" crea una ruta con nombre. Rutas con nombre:
Permita que la aplicación cree un vínculo HTTP mediante el nombre de ruta.
Se explican más adelante en el tutorial.
Valores devueltos
El método GetAll devuelve una colección de objetos TodoItem . MVC serializa automáticamente el objeto a JSON
y escribe el JSON en el cuerpo del mensaje de respuesta. El código de respuesta para este método es 200,
suponiendo que no haya ninguna excepción no controlada. Las excepciones no controladas se convierten en
errores 5xx.
En cambio, el método GetById devuelve el tipo más general IActionResult, que representa una amplia gama de
tipos de valor devuelto. GetById tiene dos tipos de valor devueltos distintos:
Si no hay ningún elemento que coincida con el identificador solicitado, el método devuelve un error 404.
Devolver NotFound genera una respuesta HTTP 404.
En caso contrario, el método devuelve 200 con un cuerpo de respuesta JSON. Devolver Ok genera una
respuesta HTTP 200.
En cambio, el método GetById devuelve el tipo ActionResult<T>, que representa una amplia gama de tipos de
valor devuelto. GetById tiene dos tipos de valor devueltos distintos:
Si no hay ningún elemento que coincida con el identificador solicitado, el método devuelve un error 404.
Devolver NotFound genera una respuesta HTTP 404.
En caso contrario, el método devuelve 200 con un cuerpo de respuesta JSON. Devolver item genera una
respuesta HTTP 200.
Iniciar la aplicación
En VS Code, presione F5 para iniciar la aplicación. Vaya a http://localhost:5000/api/todo (el controlador Todo que
se acaba de crear).
Agregue un archivo HTML denominado index.html al directorio wwwroot del proyecto. Reemplace el contenido
por el siguiente marcado:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>To-do CRUD</title>
<style>
input[type='submit'], button, [aria-label] {
cursor: pointer;
}
#spoiler {
display: none;
}
table {
font-family: Arial, sans-serif;
border: 1px solid;
border-collapse: collapse;
}
th {
background-color: #0066CC;
color: white;
}
td {
border: 1px solid;
padding: 5px;
}
</style>
</head>
<body>
<h1>To-do CRUD</h1>
<h3>Add</h3>
<form action="javascript:void(0);" method="POST" onsubmit="addItem()">
<input type="text" id="add-name" placeholder="New to-do">
<input type="submit" value="Add">
</form>
<div id="spoiler">
<h3>Edit</h3>
<form class="my-form">
<input type="hidden" id="edit-id">
<input type="checkbox" id="edit-isComplete">
<input type="text" id="edit-name">
<input type="submit" value="Edit">
<a onclick="closeInput()" aria-label="Close">✖</a>
</form>
</div>
<p id="counter"></p>
<table>
<tr>
<th>Is Complete</th>
<th>Name</th>
<th></th>
<th></th>
</tr>
<tbody id="todos"></tbody>
</table>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script src="site.js"></script>
</body>
</html>
Agregue un archivo JavaScript denominado site.js al directorio wwwroot del proyecto. Reemplace el contenido por
el siguiente código:
$(document).ready(function () {
getData();
});
function getData() {
$.ajax({
type: 'GET',
url: uri,
success: function (data) {
$('#todos').empty();
getCount(data.length);
$.each(data, function (key, item) {
const checked = item.isComplete ? 'checked' : '';
todos = data;
}
});
}
function addItem() {
const item = {
'name': $('#add-name').val(),
'isComplete': false
};
$.ajax({
type: 'POST',
accepts: 'application/json',
url: uri,
contentType: 'application/json',
data: JSON.stringify(item),
error: function (jqXHR, textStatus, errorThrown) {
alert('here');
},
success: function (result) {
getData();
$('#add-name').val('');
}
});
}
function deleteItem(id) {
$.ajax({
$.ajax({
url: uri + '/' + id,
type: 'DELETE',
success: function (result) {
getData();
}
});
}
function editItem(id) {
$.each(todos, function (key, item) {
if (item.id === id) {
$('#edit-name').val(item.name);
$('#edit-id').val(item.id);
$('#edit-isComplete').val(item.isComplete);
}
});
$('#spoiler').css({ 'display': 'block' });
}
$('.my-form').on('submit', function () {
const item = {
'name': $('#edit-name').val(),
'isComplete': $('#edit-isComplete').is(':checked'),
'id': $('#edit-id').val()
};
$.ajax({
url: uri + '/' + $('#edit-id').val(),
type: 'PUT',
accepts: 'application/json',
contentType: 'application/json',
data: JSON.stringify(item),
success: function (result) {
getData();
}
});
closeInput();
return false;
});
function closeInput() {
$('#spoiler').css({ 'display': 'none' });
}
Puede que sea necesario realizar un cambio en la configuración de inicio del proyecto de ASP.NET Core para
comprobar la página HTML localmente. Abra launchSettings.json en el directorio Properties del proyecto. Quite la
propiedad launchUrl para forzar a la aplicación a abrirse en index.html, esto es, el archivo predeterminado del
proyecto.
Existen varias formas de obtener jQuery. En el fragmento de código anterior, la biblioteca se carga desde una red
CDN. Este ejemplo es un ejemplo de CRUD completo de llamada a la API de jQuery. Existen más características en
este ejemplo para hacer que la experiencia sea más completa. Aquí verá algunas explicaciones sobre las llamadas a
la API.
Obtener una lista de tareas pendientes
Para obtener una lista de tareas pendientes, envíe una solicitud HTTP GET a /api/todo.
La función de JQuery ajax envía una solicitud AJAX a la API, que devuelve código JSON que representa un objeto
o una matriz. Esta función puede controlar todas las formas de interacción de HTTP enviando una solicitud HTTP a
la url especificada. GET se emite como type . La función de devolución de llamada success se invoca si la
solicitud se realiza correctamente. En la devolución de llamada, el DOM se actualiza con la información de la tarea
pendiente.
$(document).ready(function () {
getData();
});
function getData() {
$.ajax({
type: 'GET',
url: uri,
success: function (data) {
$('#todos').empty();
getCount(data.length);
$.each(data, function (key, item) {
const checked = item.isComplete ? 'checked' : '';
todos = data;
}
});
}
function addItem() {
const item = {
'name': $('#add-name').val(),
'isComplete': false
};
$.ajax({
type: 'POST',
accepts: 'application/json',
url: uri,
contentType: 'application/json',
data: JSON.stringify(item),
error: function (jqXHR, textStatus, errorThrown) {
alert('here');
},
success: function (result) {
getData();
$('#add-name').val('');
}
});
}
$.ajax({
url: uri + '/' + $('#edit-id').val(),
type: 'PUT',
accepts: 'application/json',
contentType: 'application/json',
data: JSON.stringify(item),
success: function (result) {
getData();
}
});
$.ajax({
url: uri + '/' + id,
type: 'DELETE',
success: function (result) {
getData();
}
});
[!code-csharp]
[!code-csharp]
Usar Postman para enviar una solicitud de creación
Inicia la aplicación.
Abra Postman.
Actualice el número de puerto en la dirección URL de localhost.
Establezca el método HTTP en POST.
Haga clic en la pestaña Body (Cuerpo).
Seleccione el botón de radio Raw (Sin formato).
Establezca el tipo en JSON (application/json).
Escriba un cuerpo de solicitud con una tarea pendiente parecida al siguiente JSON:
{
"name":"walk dog",
"isComplete":true
}
TIP
Si no aparece ninguna respuesta tras hacer clic en Send (Enviar), deshabilite la opción SSL certification verification
(Comprobación de certificación SSL). La encontrará en File > Settings (Archivo > Configuración). Vuelva a hacer clic en el
botón Send (Enviar) después de deshabilitar la configuración.
Haga clic en la pestaña Headers (Encabezados) del panel Response (Respuesta) y copie el valor de encabezado de
Location (Ubicación):
El URI del encabezado de ubicación puede utilizarse para acceder al nuevo elemento.
Actualizar
Agregue el siguiente método Update :
[!code-csharp]
[!code-csharp]
Update es similar a Create , salvo por el hecho de que usa HTTP PUT. La respuesta es 204 Sin contenido. Según la
especificación HTTP, una solicitud PUT requiere que el cliente envíe toda la entidad actualizada, no solo los deltas.
Para admitir actualizaciones parciales, use HTTP PATCH.
Use Postman para actualizar el nombre de la tarea pendiente a "walk cat":
Eliminar
Agregue el siguiente método Delete :
[HttpDelete("{id}")]
public IActionResult Delete(long id)
{
var todo = _context.TodoItems.Find(id);
if (todo == null)
{
return NotFound();
}
_context.TodoItems.Remove(todo);
_context.SaveChanges();
return NoContent();
}
Pasos siguientes
Para más información sobre cómo usar una base de datos persistente, vea:
Creación de una aplicación web de páginas de Razor con ASP.NET Core
Trabajo con datos en ASP.NET Core
Páginas de ayuda de ASP.NET Core Web API mediante Swagger
Enrutamiento a acciones del controlador
Compilación de API web con ASP.NET Core
Tipos de valor devuelto de acción del controlador
Para obtener información sobre la implementación de una API, como en Azure App Service, vea la
documentación sobre Hospedaje e implementación.
Vea o descargue el código de ejemplo. Vea cómo descargarlo.
Crear una API web con ASP.NET Core y Visual Studio
para Mac
25/06/2018 • 25 minutes to read • Edit Online
Información general
En este tutorial se crea la siguiente API:
GET /api/todo Obtener todas las tareas Ninguna Matriz de tareas pendientes
pendientes
El cliente es todo lo que consume la API web (aplicación móvil, explorador, etcétera). En este tutorial no se
crea ningún cliente. Postman o curl se utilizan como el cliente para probar la aplicación.
Un modelo es un objeto que representa los datos de la aplicación. En este caso, el único modelo es una tarea
pendiente. Los modelos se representan como clases de C#, también conocidas como clases POCO (del
inglés Plain Old C# Object, objetos CRL antiguos sin formato).
Un controlador es un objeto que controla solicitudes HTTP y crea la respuesta HTTP. Esta aplicación tiene un
único controlador.
Para simplificar el tutorial, la aplicación no usa una base de datos persistente. La aplicación de ejemplo
almacena tareas pendientes en una base de datos en memoria.
Vea Introduction to ASP.NET Core MVC on macOS or Linux (Introducción a ASP.NET Core MVC en macOS o
Linux) para obtener un ejemplo en el que se usa una base de datos persistente.
Requisitos previos
Visual Studio for Mac
Crear el proyecto
En Visual Studio, seleccione Archivo > Nueva solución.
Seleccione Aplicación .NET Core > API web de ASP.NET Core > Siguiente.
Escriba TodoApi en Nombre del proyecto y haga clic en Crear.
Iniciar la aplicación
En Visual Studio, seleccione Ejecutar > Iniciar con depuración para iniciar la aplicación. Visual Studio inicia un
explorador y se desplaza a http://localhost:5000 . Obtendrá un error HTTP 404 (No encontrado). Cambie la
dirección URL a http://localhost:<port>/api/values . Se muestran los datos de ValuesController :
["value1","value2"]
Haga clic con el botón derecho en la carpeta Modelos y seleccione Agregar > Nuevo archivo > General > Clase
vacía. Denomine la clase TodoItem y, después, haga clic en Nuevo.
Reemplace el código generado por el siguiente:
namespace TodoApi.Models
{
public class TodoItem
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
}
}
using Microsoft.EntityFrameworkCore;
namespace TodoApi.Models
{
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}
Agregar un controlador
En el Explorador de soluciones, en la carpeta Controladores, agregue la clase TodoController .
Reemplace el código generado con el siguiente:
[!code-csharp]
El código anterior define una clase de controlador de API sin métodos. En las secciones siguientes, se agregan
métodos para implementar la API.
[!code-csharp]
El código anterior define una clase de controlador de API sin métodos. En las secciones siguientes, se agregan
métodos para implementar la API. La clase se anota con un atributo [ApiController] para habilitar algunas
características muy prácticas. Para más información sobre las características que el atributo habilita, vea Anotación
de una clase con ApiControllerAttribute.
El constructor del controlador usa la inserción de dependencias para insertar el contexto de base de datos (
TodoContext ) en el controlador. El contexto de base de datos se usa en cada uno de los métodos CRUD del
controlador. El constructor agrega un elemento a la base de datos en memoria si no existe ninguno.
Tareas pendientes
Para obtener tareas pendientes, agregue estos métodos a la clase TodoController :
[!code-csharp]
[!code-csharp]
Estos métodos implementan los dos métodos GET:
GET /api/todo
GET /api/todo/{id}
[
{
"id": 1,
"name": "Item1",
"isComplete": false
}
]
Más adelante en el tutorial, veremos cómo se puede ver la respuesta HTTP por medio de Postman o curl.
Enrutamiento y rutas URL
El atributo [HttpGet] indica un método que responde a una solicitud HTTP GET. La ruta de dirección URL para
cada método se construye como sigue:
Tome la cadena de plantilla en el atributo Route del controlador:
[!code-csharp]
[!code-csharp]
Reemplace [controller] por el nombre del controlador, que es el nombre de clase de controlador sin el sufijo
"Controller". En este ejemplo, el nombre de clase de controlador es TodoController y el nombre de raíz es
"todo". El enrutamiento en ASP.NET Core no distingue entre mayúsculas y minúsculas.
Si el atributo [HttpGet] tiene una plantilla de ruta (como [HttpGet("/products")] ), anexiónela a la ruta de
acceso. En este ejemplo no se usa una plantilla. Para más información, vea Enrutamiento mediante atributos con
atributos Http[Verb].
En el siguiente método GetById , "{id}" es una variable de marcador de posición correspondiente al identificador
único de la tarea pendiente. Cuando GetById se invoca, asigna el valor "{id}" de la dirección URL al parámetro
id del método.
[!code-csharp]
[!code-csharp]
Name = "GetTodo" crea una ruta con nombre. Rutas con nombre:
Permita que la aplicación cree un vínculo HTTP mediante el nombre de ruta.
Se explican más adelante en el tutorial.
Valores devueltos
El método GetAll devuelve una colección de objetos TodoItem . MVC serializa automáticamente el objeto a JSON
y escribe el JSON en el cuerpo del mensaje de respuesta. El código de respuesta para este método es 200,
suponiendo que no haya ninguna excepción no controlada. Las excepciones no controladas se convierten en
errores 5xx.
En cambio, el método GetById devuelve el tipo más general IActionResult, que representa una amplia gama de
tipos de valor devuelto. GetById tiene dos tipos de valor devueltos distintos:
Si no hay ningún elemento que coincida con el identificador solicitado, el método devuelve un error 404.
Devolver NotFound genera una respuesta HTTP 404.
En caso contrario, el método devuelve 200 con un cuerpo de respuesta JSON. Devolver Ok genera una
respuesta HTTP 200.
En cambio, el método GetById devuelve el tipo ActionResult<T>, que representa una amplia gama de tipos de
valor devuelto. GetById tiene dos tipos de valor devueltos distintos:
Si no hay ningún elemento que coincida con el identificador solicitado, el método devuelve un error 404.
Devolver NotFound genera una respuesta HTTP 404.
En caso contrario, el método devuelve 200 con un cuerpo de respuesta JSON. Devolver item genera una
respuesta HTTP 200.
Iniciar la aplicación
En Visual Studio, seleccione Ejecutar > Iniciar con depuración para iniciar la aplicación. Visual Studio inicia un
explorador y navega hasta http://localhost:<port> , donde <port> es un número de puerto elegido
aleatoriamente. Obtendrá un error HTTP 404 (No encontrado). Cambie la dirección URL a
http://localhost:<port>/api/values . Se muestran los datos de ValuesController :
["value1","value2"]
[{"key":1,"name":"Item1","isComplete":false}]
Implementar las otras operaciones CRUD
Vamos a agregar los métodos Create , Update y Delete al controlador. Estos métodos son variaciones de un
tema, así que solo mostraré el código y comentaré las diferencias principales. Compile el proyecto después de
agregar o cambiar el código.
Crear
[!code-csharp]
El código anterior responde a un método HTTP POST, como se puede apreciar por el atributo [HttpPost]. El
atributo [FromBody] indica a MVC que obtenga el valor de la tarea pendiente del cuerpo de la solicitud HTTP.
[!code-csharp]
El código anterior responde a un método HTTP POST, como se puede apreciar por el atributo [HttpPost]. MVC
obtiene el valor de la tarea pendiente del cuerpo de la solicitud HTTP.
El método CreatedAtRoute devuelve una respuesta 201. Se trata de la respuesta estándar de un método HTTP
POST que crea un recurso en el servidor. CreatedAtRoute también agrega un encabezado de ubicación a la
respuesta. El encabezado de ubicación especifica el URI de la tarea pendiente recién creada. Vea 10.2.2 201 Created
(10.2.2 201 creada).
Usar Postman para enviar una solicitud de creación
Inicie la aplicación (Ejecutar > Iniciar con depuración).
Abra Postman.
{
"name":"walk dog",
"isComplete":true
}
TIP
Si no aparece ninguna respuesta tras hacer clic en Send (Enviar), deshabilite la opción SSL certification verification
(Comprobación de certificación SSL). La encontrará en File > Settings (Archivo > Configuración). Vuelva a hacer clic en el
botón Send (Enviar) después de deshabilitar la configuración.
Haga clic en la pestaña Headers (Encabezados) del panel Response (Respuesta) y copie el valor de encabezado de
Location (Ubicación):
Puede usar el URI del encabezado Location (Ubicación) para tener acceso al recurso que ha creado. El método
Create devuelve CreatedAtRoute. El primer parámetro que se pasa a CreatedAtRoute representa la ruta con
nombre que se usa para generar la dirección URL. Recuerde que el método GetById creó la ruta con nombre
"GetTodo" :
Actualizar
[!code-csharp]
[!code-csharp]
Update es similar a Create , pero usa HTTP PUT. La respuesta es 204 Sin contenido. Según la especificación HTTP,
una solicitud PUT requiere que el cliente envíe toda la entidad actualizada, no solo los deltas. Para admitir
actualizaciones parciales, use HTTP PATCH.
{
"key": 1,
"name": "walk dog",
"isComplete": true
}
Eliminar
[HttpDelete("{id}")]
public IActionResult Delete(long id)
{
var todo = _context.TodoItems.Find(id);
if (todo == null)
{
return NotFound();
}
_context.TodoItems.Remove(todo);
_context.SaveChanges();
return NoContent();
}
Agregue un archivo HTML denominado index.html al directorio wwwroot del proyecto. Reemplace el contenido
por el siguiente marcado:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>To-do CRUD</title>
<style>
input[type='submit'], button, [aria-label] {
cursor: pointer;
}
#spoiler {
display: none;
}
table {
font-family: Arial, sans-serif;
border: 1px solid;
border-collapse: collapse;
}
th {
background-color: #0066CC;
color: white;
}
td {
border: 1px solid;
padding: 5px;
}
</style>
</head>
<body>
<h1>To-do CRUD</h1>
<h3>Add</h3>
<form action="javascript:void(0);" method="POST" onsubmit="addItem()">
<input type="text" id="add-name" placeholder="New to-do">
<input type="submit" value="Add">
</form>
<div id="spoiler">
<h3>Edit</h3>
<form class="my-form">
<input type="hidden" id="edit-id">
<input type="checkbox" id="edit-isComplete">
<input type="text" id="edit-name">
<input type="submit" value="Edit">
<a onclick="closeInput()" aria-label="Close">✖</a>
</form>
</div>
<p id="counter"></p>
<table>
<tr>
<th>Is Complete</th>
<th>Name</th>
<th>Name</th>
<th></th>
<th></th>
</tr>
<tbody id="todos"></tbody>
</table>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script src="site.js"></script>
</body>
</html>
Agregue un archivo JavaScript denominado site.js al directorio wwwroot del proyecto. Reemplace el contenido por
el siguiente código:
$(document).ready(function () {
getData();
});
function getData() {
$.ajax({
type: 'GET',
url: uri,
success: function (data) {
$('#todos').empty();
getCount(data.length);
$.each(data, function (key, item) {
const checked = item.isComplete ? 'checked' : '';
todos = data;
}
});
}
function addItem() {
const item = {
'name': $('#add-name').val(),
'isComplete': false
};
$.ajax({
type: 'POST',
accepts: 'application/json',
url: uri,
url: uri,
contentType: 'application/json',
data: JSON.stringify(item),
error: function (jqXHR, textStatus, errorThrown) {
alert('here');
},
success: function (result) {
getData();
$('#add-name').val('');
}
});
}
function deleteItem(id) {
$.ajax({
url: uri + '/' + id,
type: 'DELETE',
success: function (result) {
getData();
}
});
}
function editItem(id) {
$.each(todos, function (key, item) {
if (item.id === id) {
$('#edit-name').val(item.name);
$('#edit-id').val(item.id);
$('#edit-isComplete').val(item.isComplete);
}
});
$('#spoiler').css({ 'display': 'block' });
}
$('.my-form').on('submit', function () {
const item = {
'name': $('#edit-name').val(),
'isComplete': $('#edit-isComplete').is(':checked'),
'id': $('#edit-id').val()
};
$.ajax({
url: uri + '/' + $('#edit-id').val(),
type: 'PUT',
accepts: 'application/json',
contentType: 'application/json',
data: JSON.stringify(item),
success: function (result) {
getData();
}
});
closeInput();
return false;
});
function closeInput() {
$('#spoiler').css({ 'display': 'none' });
}
Puede que sea necesario realizar un cambio en la configuración de inicio del proyecto de ASP.NET Core para
comprobar la página HTML localmente. Abra launchSettings.json en el directorio Properties del proyecto. Quite la
propiedad launchUrl para forzar a la aplicación a abrirse en index.html, esto es, el archivo predeterminado del
proyecto.
Existen varias formas de obtener jQuery. En el fragmento de código anterior, la biblioteca se carga desde una red
CDN. Este ejemplo es un ejemplo de CRUD completo de llamada a la API de jQuery. Existen más características en
este ejemplo para hacer que la experiencia sea más completa. Aquí verá algunas explicaciones sobre las llamadas a
la API.
Obtener una lista de tareas pendientes
Para obtener una lista de tareas pendientes, envíe una solicitud HTTP GET a /api/todo.
La función de JQuery ajax envía una solicitud AJAX a la API, que devuelve código JSON que representa un objeto
o una matriz. Esta función puede controlar todas las formas de interacción de HTTP enviando una solicitud HTTP a
la url especificada. GET se emite como type . La función de devolución de llamada success se invoca si la
solicitud se realiza correctamente. En la devolución de llamada, el DOM se actualiza con la información de la tarea
pendiente.
$(document).ready(function () {
getData();
});
function getData() {
$.ajax({
type: 'GET',
url: uri,
success: function (data) {
$('#todos').empty();
getCount(data.length);
$.each(data, function (key, item) {
const checked = item.isComplete ? 'checked' : '';
todos = data;
}
});
}
$.ajax({
type: 'POST',
accepts: 'application/json',
url: uri,
contentType: 'application/json',
data: JSON.stringify(item),
error: function (jqXHR, textStatus, errorThrown) {
alert('here');
},
success: function (result) {
getData();
$('#add-name').val('');
}
});
}
$.ajax({
url: uri + '/' + $('#edit-id').val(),
type: 'PUT',
accepts: 'application/json',
contentType: 'application/json',
data: JSON.stringify(item),
success: function (result) {
getData();
}
});
$.ajax({
url: uri + '/' + id,
type: 'DELETE',
success: function (result) {
getData();
}
});
Pasos siguientes
Para más información sobre cómo usar una base de datos persistente, vea:
Creación de una aplicación web de páginas de Razor con ASP.NET Core
Trabajo con datos en ASP.NET Core
Páginas de ayuda de ASP.NET Core Web API mediante Swagger
Enrutamiento a acciones del controlador
Compilación de API web con ASP.NET Core
Tipos de valor devuelto de acción del controlador
Para obtener información sobre la implementación de una API, como en Azure App Service, vea la
documentación sobre Hospedaje e implementación.
Vea o descargue el código de ejemplo. Vea cómo descargarlo.
Crear una API web con ASP.NET Core y Visual
Studio para Windows
21/06/2018 • 25 minutes to read • Edit Online
Información general
En este tutorial se crea la siguiente API:
GET /api/todo Obtener todas las tareas Ninguna Matriz de tareas pendientes
pendientes
El cliente es todo lo que consume la API web (aplicación móvil, explorador, etcétera). En este tutorial no se
crea ningún cliente. Postman o curl se utilizan como el cliente para probar la aplicación.
Un modelo es un objeto que representa los datos de la aplicación. En este caso, el único modelo es una
tarea pendiente. Los modelos se representan como clases de C#, también conocidas como clases POCO
(del inglés Plain Old C# Object, objetos CRL antiguos sin formato).
Un controlador es un objeto que controla solicitudes HTTP y crea la respuesta HTTP. Esta aplicación tiene
un único controlador.
Para simplificar el tutorial, la aplicación no usa una base de datos persistente. La aplicación de ejemplo
almacena tareas pendientes en una base de datos en memoria.
Requisitos previos
Visual Studio for Windows.
Select the ASP.NET and web development workload.
.Net Core 2.1 SDK
Crear el proyecto
Haga lo siguiente para descargar Visual Studio:
En el menú Archivo, seleccione Nuevo > Proyecto.
Seleccione la plantilla Aplicación web ASP.NET Core. Denomine el proyecto TodoApi y haga clic en
Aceptar.
En el cuadro de diálogo Nueva aplicación web ASP.NET Core - TodoApi, seleccione la versión ASP.NET
Core. Seleccione la plantilla API y haga clic en Aceptar. No seleccione Habilitar compatibilidad con
Docker.
Iniciar la aplicación
En Visual Studio, presione CTRL+F5 para iniciar la aplicación. Visual Studio inicia un explorador y navega hasta
http://localhost:<port>/api/values , donde <port> es un número de puerto elegido aleatoriamente. En
Chrome, Microsoft Edge y Firefox se muestra la salida siguiente:
["value1","value2"]
NOTE
Las clases del modelo pueden ir en cualquier parte del proyecto. La carpeta Models se usa por convención para las clases
de modelos.
En el Explorador de soluciones, haga clic con el botón derecho en la carpeta de modelos y seleccione Agregar >
Clase. Denomine la clase TodoItem y, después, haga clic en Agregar.
Actualice la clase TodoItem por el siguiente código:
namespace TodoApi.Models
{
public class TodoItem
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
}
}
En el Explorador de soluciones, haga clic con el botón derecho en la carpeta de modelos y seleccione Agregar >
Clase. Denomine la clase TodoContext y, después, haga clic en Agregar.
Reemplace la clase por el siguiente código:
using Microsoft.EntityFrameworkCore;
namespace TodoApi.Models
{
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}
Tareas pendientes
Para obtener tareas pendientes, agregue estos métodos a la clase TodoController :
[!code-csharp]
[!code-csharp]
Estos métodos implementan los dos métodos GET:
GET /api/todo
GET /api/todo/{id}
Más adelante en el tutorial, veremos cómo se puede ver la respuesta HTTP por medio de Postman o curl.
Enrutamiento y rutas URL
El atributo [HttpGet] indica un método que responde a una solicitud HTTP GET. La ruta de dirección URL para
cada método se construye como sigue:
Tome la cadena de plantilla en el atributo Route del controlador:
[!code-csharp]
[!code-csharp]
Reemplace [controller] por el nombre del controlador, que es el nombre de clase de controlador sin el
sufijo "Controller". En este ejemplo, el nombre de clase de controlador es TodoController y el nombre de raíz
es "todo". El enrutamiento en ASP.NET Core no distingue entre mayúsculas y minúsculas.
Si el atributo [HttpGet] tiene una plantilla de ruta (como [HttpGet("/products")] ), anexiónela a la ruta de
acceso. En este ejemplo no se usa una plantilla. Para más información, vea Enrutamiento mediante atributos
con atributos Http[Verb].
En el siguiente método GetById , "{id}" es una variable de marcador de posición correspondiente al
identificador único de la tarea pendiente. Cuando GetById se invoca, asigna el valor "{id}" de la dirección
URL al parámetro id del método.
[!code-csharp]
[!code-csharp]
Name = "GetTodo" crea una ruta con nombre. Rutas con nombre:
Permita que la aplicación cree un vínculo HTTP mediante el nombre de ruta.
Se explican más adelante en el tutorial.
Valores devueltos
El método GetAll devuelve una colección de objetos TodoItem . MVC serializa automáticamente el objeto a
JSON y escribe el JSON en el cuerpo del mensaje de respuesta. El código de respuesta para este método es
200, suponiendo que no haya ninguna excepción no controlada. Las excepciones no controladas se convierten
en errores 5xx.
En cambio, el método GetById devuelve el tipo más general IActionResult, que representa una amplia gama de
tipos de valor devuelto. GetById tiene dos tipos de valor devueltos distintos:
Si no hay ningún elemento que coincida con el identificador solicitado, el método devuelve un error 404.
Devolver NotFound genera una respuesta HTTP 404.
En caso contrario, el método devuelve 200 con un cuerpo de respuesta JSON. Devolver Ok genera una
respuesta HTTP 200.
En cambio, el método GetById devuelve el tipo ActionResult<T>, que representa una amplia gama de tipos de
valor devuelto. GetById tiene dos tipos de valor devueltos distintos:
Si no hay ningún elemento que coincida con el identificador solicitado, el método devuelve un error 404.
Devolver NotFound genera una respuesta HTTP 404.
En caso contrario, el método devuelve 200 con un cuerpo de respuesta JSON. Devolver item genera una
respuesta HTTP 200.
Iniciar la aplicación
En Visual Studio, presione CTRL+F5 para iniciar la aplicación. Visual Studio inicia un explorador y navega hasta
http://localhost:<port>/api/values , donde <port> es un número de puerto elegido aleatoriamente. Vaya al
controlador Todo en http://localhost:<port>/api/todo .
[!code-csharp]
[!code-csharp]
Usar Postman para enviar una solicitud de creación
Inicia la aplicación.
Abra Postman.
Actualice el número de puerto en la dirección URL de localhost.
Establezca el método HTTP en POST.
Haga clic en la pestaña Body (Cuerpo).
Seleccione el botón de radio Raw (Sin formato).
Establezca el tipo en JSON (application/json).
Escriba un cuerpo de solicitud con una tarea pendiente parecida al siguiente JSON:
{
"name":"walk dog",
"isComplete":true
}
TIP
Si no aparece ninguna respuesta tras hacer clic en Send (Enviar), deshabilite la opción SSL certification verification
(Comprobación de certificación SSL). La encontrará en File > Settings (Archivo > Configuración). Vuelva a hacer clic en el
botón Send (Enviar) después de deshabilitar la configuración.
Haga clic en la pestaña Headers (Encabezados) del panel Response (Respuesta) y copie el valor de encabezado
de Location (Ubicación):
El URI del encabezado de ubicación puede utilizarse para acceder al nuevo elemento.
Actualizar
Agregue el siguiente método Update :
[!code-csharp]
[!code-csharp]
Update es similar a Create , salvo por el hecho de que usa HTTP PUT. La respuesta es 204 Sin contenido.
Según la especificación HTTP, una solicitud PUT requiere que el cliente envíe toda la entidad actualizada, no solo
los deltas. Para admitir actualizaciones parciales, use HTTP PATCH.
Use Postman para actualizar el nombre de la tarea pendiente a "walk cat":
Eliminar
Agregue el siguiente método Delete :
[HttpDelete("{id}")]
public IActionResult Delete(long id)
{
var todo = _context.TodoItems.Find(id);
if (todo == null)
{
return NotFound();
}
_context.TodoItems.Remove(todo);
_context.SaveChanges();
return NoContent();
}
Agregue un archivo HTML denominado index.html al directorio wwwroot del proyecto. Reemplace el contenido
por el siguiente marcado:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>To-do CRUD</title>
<style>
input[type='submit'], button, [aria-label] {
cursor: pointer;
}
#spoiler {
display: none;
}
table {
font-family: Arial, sans-serif;
border: 1px solid;
border-collapse: collapse;
}
th {
background-color: #0066CC;
color: white;
}
td {
border: 1px solid;
padding: 5px;
}
</style>
</head>
<body>
<h1>To-do CRUD</h1>
<h3>Add</h3>
<form action="javascript:void(0);" method="POST" onsubmit="addItem()">
<input type="text" id="add-name" placeholder="New to-do">
<input type="submit" value="Add">
</form>
<div id="spoiler">
<h3>Edit</h3>
<form class="my-form">
<input type="hidden" id="edit-id">
<input type="checkbox" id="edit-isComplete">
<input type="text" id="edit-name">
<input type="submit" value="Edit">
<a onclick="closeInput()" aria-label="Close">✖</a>
</form>
</div>
<p id="counter"></p>
<table>
<tr>
<th>Is Complete</th>
<th>Name</th>
<th></th>
<th></th>
</tr>
<tbody id="todos"></tbody>
</table>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script src="site.js"></script>
</body>
</html>
Agregue un archivo JavaScript denominado site.js al directorio wwwroot del proyecto. Reemplace el contenido
por el siguiente código:
$(document).ready(function () {
getData();
});
function getData() {
$.ajax({
type: 'GET',
url: uri,
success: function (data) {
$('#todos').empty();
getCount(data.length);
$.each(data, function (key, item) {
const checked = item.isComplete ? 'checked' : '';
todos = data;
}
});
}
function addItem() {
const item = {
'name': $('#add-name').val(),
'isComplete': false
};
$.ajax({
type: 'POST',
accepts: 'application/json',
url: uri,
contentType: 'application/json',
data: JSON.stringify(item),
error: function (jqXHR, textStatus, errorThrown) {
alert('here');
},
success: function (result) {
getData();
$('#add-name').val('');
}
});
}
function deleteItem(id) {
$.ajax({
url: uri + '/' + id,
type: 'DELETE',
success: function (result) {
getData();
}
});
}
function editItem(id) {
function editItem(id) {
$.each(todos, function (key, item) {
if (item.id === id) {
$('#edit-name').val(item.name);
$('#edit-id').val(item.id);
$('#edit-isComplete').val(item.isComplete);
}
});
$('#spoiler').css({ 'display': 'block' });
}
$('.my-form').on('submit', function () {
const item = {
'name': $('#edit-name').val(),
'isComplete': $('#edit-isComplete').is(':checked'),
'id': $('#edit-id').val()
};
$.ajax({
url: uri + '/' + $('#edit-id').val(),
type: 'PUT',
accepts: 'application/json',
contentType: 'application/json',
data: JSON.stringify(item),
success: function (result) {
getData();
}
});
closeInput();
return false;
});
function closeInput() {
$('#spoiler').css({ 'display': 'none' });
}
Puede que sea necesario realizar un cambio en la configuración de inicio del proyecto de ASP.NET Core para
comprobar la página HTML localmente. Abra launchSettings.json en el directorio Properties del proyecto. Quite
la propiedad launchUrl para forzar a la aplicación a abrirse en index.html, esto es, el archivo predeterminado
del proyecto.
Existen varias formas de obtener jQuery. En el fragmento de código anterior, la biblioteca se carga desde una
red CDN. Este ejemplo es un ejemplo de CRUD completo de llamada a la API de jQuery. Existen más
características en este ejemplo para hacer que la experiencia sea más completa. Aquí verá algunas explicaciones
sobre las llamadas a la API.
Obtener una lista de tareas pendientes
Para obtener una lista de tareas pendientes, envíe una solicitud HTTP GET a /api/todo.
La función de JQuery ajax envía una solicitud AJAX a la API, que devuelve código JSON que representa un
objeto o una matriz. Esta función puede controlar todas las formas de interacción de HTTP enviando una
solicitud HTTP a la url especificada. GET se emite como type . La función de devolución de llamada success
se invoca si la solicitud se realiza correctamente. En la devolución de llamada, el DOM se actualiza con la
información de la tarea pendiente.
$(document).ready(function () {
getData();
});
function getData() {
$.ajax({
type: 'GET',
url: uri,
success: function (data) {
$('#todos').empty();
getCount(data.length);
$.each(data, function (key, item) {
const checked = item.isComplete ? 'checked' : '';
todos = data;
}
});
}
function addItem() {
const item = {
'name': $('#add-name').val(),
'isComplete': false
};
$.ajax({
type: 'POST',
accepts: 'application/json',
url: uri,
contentType: 'application/json',
data: JSON.stringify(item),
error: function (jqXHR, textStatus, errorThrown) {
alert('here');
},
success: function (result) {
getData();
$('#add-name').val('');
}
});
}
$.ajax({
url: uri + '/' + $('#edit-id').val(),
type: 'PUT',
accepts: 'application/json',
contentType: 'application/json',
data: JSON.stringify(item),
success: function (result) {
getData();
}
});
$.ajax({
url: uri + '/' + id,
type: 'DELETE',
success: function (result) {
getData();
}
});
Pasos siguientes
Para más información sobre cómo usar una base de datos persistente, vea:
Creación de una aplicación web de páginas de Razor con ASP.NET Core
Trabajo con datos en ASP.NET Core
Páginas de ayuda de ASP.NET Core Web API mediante Swagger
Enrutamiento a acciones del controlador
Compilación de API web con ASP.NET Core
Tipos de valor devuelto de acción del controlador
Para obtener información sobre la implementación de una API, como en Azure App Service, vea la
documentación sobre Hospedaje e implementación.
Vea o descargue el código de ejemplo. Vea cómo descargarlo.
Crear servicios back-end para aplicaciones móviles
nativas con ASP.NET Core
25/06/2018 • 14 minutes to read • Edit Online
Al pulsar un elemento en la pantalla de la lista principal se abre un cuadro de diálogo de edición, donde se puede
modificar el nombre del elemento, las notas y la configuración de Done (Listo), o se puede eliminar el elemento:
Este ejemplo está configurado para usar de forma predeterminada servicios back-end hospedados en
developer.xamarin.com, lo que permite operaciones de solo lectura. Para probarlo usted mismo con la aplicación de
ASP.NET Core que creó en la siguiente sección que se ejecuta en el equipo, debe actualizar la constante RestUrl de
la aplicación. Vaya hasta el proyecto ToDoREST y abra el archivo Constants.cs. Reemplace RestUrl con una
dirección URL que incluya la dirección IP de su equipo (no localhost ni 127.0.0.1, puesto que esta dirección se usa
desde el emulador de dispositivo, no desde el equipo). Incluya también el número de puerto (5000). Para
comprobar que los servicios funcionan con un dispositivo, asegúrese de que no tiene un firewall activo que bloquea
el acceso a este puerto.
NOTE
Asegúrese de que ejecuta la aplicación directamente, en lugar de tras IIS Express, que omite las solicitudes no locales de forma
predeterminada. Ejecute dotnet run desde un símbolo del sistema o elija el perfil del nombre de aplicación en la lista
desplegable de destino de depuración en la barra de herramientas de Visual Studio.
Agregue una clase de modelo para representar las tareas pendientes. Marque los campos obligatorios mediante el
atributo [Required] :
using System.ComponentModel.DataAnnotations;
namespace ToDoApi.Models
{
public class ToDoItem
{
[Required]
public string ID { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Notes { get; set; }
Los métodos de API necesitan alguna manera de trabajar con los datos. Use la misma interfaz de IToDoRepository
que usa el ejemplo original de Xamarin:
using System.Collections.Generic;
using ToDoApi.Models;
namespace ToDoApi.Interfaces
{
public interface IToDoRepository
{
bool DoesItemExist(string id);
IEnumerable<ToDoItem> All { get; }
ToDoItem Find(string id);
void Insert(ToDoItem item);
void Update(ToDoItem item);
void Delete(string id);
}
}
using System.Collections.Generic;
using System.Linq;
using ToDoApi.Interfaces;
using ToDoApi.Models;
namespace ToDoApi.Services
{
public class ToDoRepository : IToDoRepository
{
private List<ToDoItem> _toDoList;
public ToDoRepository()
{
InitializeData();
}
_toDoList.Add(todoItem1);
_toDoList.Add(todoItem2);
_toDoList.Add(todoItem3);
}
}
}
services.AddSingleton<IToDoRepository,ToDoRepository>();
}
TIP
Obtenga más información sobre cómo crear API web en Cree su primera API web con ASP.NET Core MVC y Visual Studio.
Crear el controlador
Agregue un nuevo controlador para el proyecto: ToDoItemsController. Debe heredar de
Microsoft.AspNetCore.Mvc.Controller. Agregue un atributo Route para indicar que el controlador controlará las
solicitudes realizadas a las rutas de acceso que comiencen con api/todoitems . El token [controller] de la ruta se
sustituye por el nombre del controlador (si se omite el sufijo Controller ) y es especialmente útil para las rutas
globales. Obtenga más información sobre el enrutamiento.
El controlador necesita un IToDoRepository para funcionar. Solicite una instancia de este tipo a través del
constructor del controlador. En tiempo de ejecución, esta instancia se proporcionará con la compatibilidad del
marco con la inserción de dependencias.
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using ToDoApi.Interfaces;
using ToDoApi.Models;
namespace ToDoApi.Controllers
{
[Route("api/[controller]")]
public class ToDoItemsController : Controller
{
private readonly IToDoRepository _toDoRepository;
Esta API es compatible con cuatro verbos HTTP diferentes para realizar operaciones CRUD (creación, lectura,
actualización, eliminación) en el origen de datos. La más simple de ellas es la operación de lectura, que corresponde
a una solicitud HTTP GET.
Leer elementos
La solicitud de una lista de elementos se realiza con una solicitud GET al método List . El atributo [HttpGet] en el
método List indica que esta acción solo debe controlar las solicitudes GET. La ruta de esta acción es la ruta
especificada en el controlador. No es necesario usar el nombre de acción como parte de la ruta. Solo debe
asegurarse de que cada acción tiene una ruta única e inequívoca. El enrutamiento de atributos se puede aplicar
tanto a los niveles de controlador como de método para crear rutas específicas.
[HttpGet]
public IActionResult List()
{
return Ok(_toDoRepository.All);
}
El método List devuelve un código de respuesta 200 OK y todos los elementos de lista de tareas, serializados
como JSON.
Puede probar el nuevo método de API con una variedad de herramientas, como Postman, que se muestra a
continuación:
Crear elementos
Por convención, la creación de elementos de datos se asigna al verbo HTTP POST. El método Create tiene un
atributo [HttpPost] aplicado y acepta una instancia ToDoItem . Puesto que el argumento item se pasará en el
cuerpo de la solicitud POST, este parámetro se decora con el atributo [FromBody] .
Dentro del método, se comprueba la validez del elemento y si existió anteriormente en el almacén de datos y, si no
hay problemas, se agrega mediante el repositorio. Al comprobar ModelState.IsValid se realiza una validación de
modelos, y debe realizarse en cada método de API que acepte datos proporcionados por usuario.
[HttpPost]
public IActionResult Create([FromBody] ToDoItem item)
{
try
{
if (item == null || !ModelState.IsValid)
{
return BadRequest(ErrorCode.TodoItemNameAndNotesRequired.ToString());
}
bool itemExists = _toDoRepository.DoesItemExist(item.ID);
if (itemExists)
{
return StatusCode(StatusCodes.Status409Conflict, ErrorCode.TodoItemIDInUse.ToString());
}
_toDoRepository.Insert(item);
}
catch (Exception)
{
return BadRequest(ErrorCode.CouldNotCreateItem.ToString());
}
return Ok(item);
}
El ejemplo usa una enumeración que contiene códigos de error que se pasan al cliente móvil:
Pruebe a agregar nuevos elementos con Postman eligiendo el verbo POST que proporciona el nuevo objeto en
formato JSON en el cuerpo de la solicitud. También debe agregar un encabezado de solicitud que especifica un
Content-Type de application/json .
El método devuelve el elemento recién creado en la respuesta.
Actualizar elementos
La modificación de registros se realiza mediante solicitudes HTTP PUT. Aparte de este cambio, el método Edit es
casi idéntico a Create . Tenga en cuenta que, si no se encuentra el registro, la acción Edit devolverá una respuesta
NotFound (404 ).
[HttpPut]
public IActionResult Edit([FromBody] ToDoItem item)
{
try
{
if (item == null || !ModelState.IsValid)
{
return BadRequest(ErrorCode.TodoItemNameAndNotesRequired.ToString());
}
var existingItem = _toDoRepository.Find(item.ID);
if (existingItem == null)
{
return NotFound(ErrorCode.RecordNotFound.ToString());
}
_toDoRepository.Update(item);
}
catch (Exception)
{
return BadRequest(ErrorCode.CouldNotUpdateItem.ToString());
}
return NoContent();
}
Para probar con Postman, cambie el verbo a PUT. Especifique los datos actualizados del objeto en el cuerpo de la
solicitud.
Este método devuelve una respuesta NoContent (204) cuando se realiza correctamente, para mantener la
coherencia con la API existente.
Eliminar elementos
La eliminación de registros se consigue mediante solicitudes DELETE al servicio y pasando el identificador del
elemento que va a eliminar. Al igual que con las actualizaciones, las solicitudes para elementos que no existen
recibirán respuestas NotFound . De lo contrario, las solicitudes correctas recibirán una respuesta NoContent (204).
[HttpDelete("{id}")]
public IActionResult Delete(string id)
{
try
{
var item = _toDoRepository.Find(id);
if (item == null)
{
return NotFound(ErrorCode.RecordNotFound.ToString());
}
_toDoRepository.Delete(id);
}
catch (Exception)
{
return BadRequest(ErrorCode.CouldNotDeleteItem.ToString());
}
return NoContent();
}
Tenga en cuenta que, al probar la funcionalidad de eliminar, no se necesita nada en el cuerpo de la solicitud.
Todos los métodos de acción públicos aplicados a los controladores se pueden probar desde la interfaz de
usuario. Haga clic en un nombre de método para expandir la sección. Agregue todos los parámetros necesarios y
haga clic en Try it out! (¡ Pruébelo!).
NOTE
La versión de interfaz de usuario de Swagger usada para las capturas de pantalla es la versión 2. Para obtener un ejemplo
de la versión 3, vea el ejemplo de Petstore.
Pasos siguientes
Get started with Swashbuckle (Introducción a Swashbuckle)
Get started with NSwag (Introducción a NSwag)
Trabajo con datos en ASP.NET Core
21/06/2018 • 2 minutes to read • Edit Online
Introducción a las páginas de Razor y Entity Framework Core con Visual Studio
Introducción a las páginas de Razor y EF
Operaciones de creación, lectura, actualización y eliminación
Ordenación, filtrado, paginación y agrupación
Migraciones
Creación de un modelo de datos complejo
Lectura de datos relacionados
Actualización de datos relacionados
Control de conflictos de simultaneidad
Introducción a ASP.NET Core MVC y Entity Framework Core con Visual Studio
Introducción
Operaciones de creación, lectura, actualización y eliminación
Ordenación, filtrado, paginación y agrupación
Migraciones
Creación de un modelo de datos complejo
Lectura de datos relacionados
Actualización de datos relacionados
Control de conflictos de simultaneidad
Herencia
Temas avanzados
ASP.NET Core con EF Core: nueva base de datos (sitio de la documentación de Entity Framework Core)
ASP.NET Core con EF Core: base de datos existente (sitio de la documentación de Entity Framework Core)
Introducción a ASP.NET Core y Entity Framework 6
Azure Storage
Agregar Azure Storage mediante el uso de Servicios conectados de Visual Studio
Introducción a Azure Blob Storage y Servicios conectados de Visual Studio
Introducción a Queue Storage y Servicios conectados de Visual Studio
Introducción a Azure Table Storage y Servicios conectados de Visual Studio
Páginas de Razor de ASP.NET Core con EF Core:
serie de tutoriales
21/06/2018 • 2 minutes to read • Edit Online
En esta serie de tutoriales aprenderá a crear aplicaciones web de páginas de Razor de ASP.NET Core que usen
Entity Framework (EF ) Core para acceder a los datos. Los tutoriales requieren Visual Studio 2017.
1. Introducción
2. Operaciones de creación, lectura, actualización y eliminación
3. Ordenado, filtrado, paginación y agrupación
4. Migraciones
5. Creación de un modelo de datos complejo
6. Lectura de datos relacionados
7. Actualización de datos relacionados
8. Control de conflictos de simultaneidad
Páginas de Razor con Entity Framework Core en
ASP.NET Core: Tutorial 1 de 8
25/06/2018 • 32 minutes to read • Edit Online
Requisitos previos
Install one of the following:
CLI tooling: Windows, Linux, or macOS: .NET Core SDK 2.0 or later
IDE/editor tooling
Windows: Visual Studio for Windows
ASP.NET and web development workload
.NET Core cross-platform development workload
Linux: Visual Studio Code
macOS: Visual Studio for Mac
Familiaridad con las Páginas de Razor. Los programadores nuevos deben completar Introducción a las páginas de
Razor en ASP.NET Core antes de empezar esta serie.
Solución de problemas
Si experimenta un problema que no puede resolver, por lo general podrá encontrar la solución si compara el código
con la fase completada. Para obtener una lista de errores comunes y cómo resolverlos, vea la sección de solución de
problemas del último tutorial de la serie. Si ahí no encuentra lo que necesita, puede publicar una pregunta en
StackOverflow.com para ASP.NET Core o EF Core.
TIP
Esta serie de tutoriales se basa en lo que se realiza en los tutoriales anteriores. Considere la posibilidad de guardar una copia
del proyecto después de completar correctamente cada tutorial. Si experimenta problemas, puede empezar desde el tutorial
anterior en lugar de volver al principio. Como alternativa, puede descargar una fase completada y empezar de nuevo con la
fase completada.
Seleccione ASP.NET Core 2.0 en la lista desplegable y, luego, seleccione Aplicación web.
Presione F5 para ejecutar la aplicación en modo de depuración o Ctrl-F5 para que se ejecute sin adjuntar el
depurador.
<environment include="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
</environment>
<environment exclude="Development">
<link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-
value="absolute" />
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
</environment>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-
collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a asp-page="/Index" class="navbar-brand">Contoso University</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a asp-page="/Index">Home</a></li>
<li><a asp-page="/About">About</a></li>
<li><a asp-page="/Students/Index">Students</a></li>
<li><a asp-page="/Courses/Index">Courses</a></li>
<li><a asp-page="/Instructors/Index">Instructors</a></li>
<li><a asp-page="/Departments/Index">Departments</a></li>
</ul>
</div>
</div>
</nav>
<div class="container body-content">
@RenderBody()
<hr />
<footer>
<p>© 2017 - Contoso University</p>
</footer>
</div>
En Pages/Index.cshtml, reemplace el contenido del archivo con el código siguiente para reemplazar el texto sobre
ASP.NET y MVC con texto sobre esta aplicación:
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<div class="jumbotron">
<h1>Contoso University</h1>
</div>
<div class="row">
<div class="col-md-4">
<h2>Welcome to Contoso University</h2>
<p>
Contoso University is a sample application that
demonstrates how to use Entity Framework Core in an
ASP.NET Core Razor Pages web app.
</p>
</div>
<div class="col-md-4">
<h2>Build it from scratch</h2>
<p>You can build the application by following the steps in a series of tutorials.</p>
<p><a class="btn btn-default"
href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro">
See the tutorial »</a></p>
</div>
<div class="col-md-4">
<h2>Download it</h2>
<p>You can download the completed project from GitHub.</p>
<p><a class="btn btn-default"
href="https://github.com/aspnet/Docs/tree/master/aspnetcore/data/ef-rp/intro/samples/cu-final">
See project source code »</a></p>
</div>
</div>
Presione CTRL+F5 para ejecutar el proyecto. La página principal se muestra con las pestañas creadas en los
tutoriales siguientes:
Crear el modelo de datos
Cree las clases de entidad para la aplicación Contoso University. Comience con las tres entidades siguientes:
Hay una relación uno a varios entre las entidades Student y Enrollment . Hay una relación uno a varios entre las
entidades Course y Enrollment . Un estudiante se puede inscribir en cualquier número de cursos. Un curso puede
tener cualquier número de alumnos inscritos.
En las secciones siguientes, se crea una clase para cada una de estas entidades.
La entidad Student
Cree una carpeta Models. En la carpeta Models, cree un archivo de clase denominado Student.cs con el código
siguiente:
using System;
using System.Collections.Generic;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
La propiedad ID se convierte en la columna de clave principal de la tabla de base de datos (DB ) que corresponde a
esta clase. De forma predeterminada, EF Core interpreta como la clave principal una propiedad que se denomine
ID o classnameID . En classnameID , classname es el nombre de la clase (como Student en el ejemplo anterior ).
La propiedad Enrollments es una propiedad de navegación. Las propiedades de navegación se vinculan a otras
entidades relacionadas con esta entidad. En este caso, la propiedad Enrollments de una Student entity contiene
todas las entidades Enrollment que están relacionadas con esa entidad Student . Por ejemplo, si una fila Student
de la base de datos tiene dos filas Enrollment relacionadas, la propiedad de navegación Enrollments contiene esas
dos entidades Enrollment . Una fila Enrollment relacionada es la que contiene el valor de clave principal de ese
estudiante en la columna StudentID . Por ejemplo, suponga que el estudiante con ID=1 tiene dos filas en la tabla
Enrollment . La tabla Enrollment tiene dos filas con StudentID = 1. StudentID es una clave externa en la tabla
Enrollment que especifica el estudiante en la tabla Student .
Si una propiedad de navegación puede contener varias entidades, la propiedad de navegación debe ser un tipo de
lista, como ICollection<T> . Se puede especificar ICollection<T> , o bien un tipo como List<T> o HashSet<T> .
Cuando se usa ICollection<T> , EF Core crea una colección HashSet<T> de forma predeterminada. Las propiedades
de navegación que contienen varias entidades proceden de relaciones de varios a varios y uno a varios.
La entidad Enrollment
En la carpeta Models, cree Enrollment.cs con el código siguiente:
namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}
La propiedad EnrollmentID es la clave principal. En esta entidad se usa el patrón classnameID en lugar de ID
como en la entidad Student . Normalmente, los desarrolladores eligen un patrón y lo usan en todo el modelo de
datos. En un tutorial posterior, se muestra el uso de ID sin un nombre de clase para facilitar la implementación de la
herencia en el modelo de datos.
La propiedad Grade es una enum . El signo de interrogación después de la declaración de tipo Grade indica que la
propiedad Grade acepta valores NULL. Una calificación que sea NULL es diferente de una calificación que sea
cero; NULL significa que no se conoce una calificación o que todavía no se ha asignado.
La propiedad StudentID es una clave externa y la propiedad de navegación correspondiente es Student . Una
entidad Enrollment está asociada con una entidad Student , por lo que la propiedad contiene una única entidad
Student . La entidad Student difiere de la propiedad de navegación Student.Enrollments , que contiene varias
entidades Enrollment .
La propiedad CourseID es una clave externa y la propiedad de navegación correspondiente es Course . Una
entidad Enrollment está asociada con una entidad Course .
EF Core interpreta una propiedad como una clave externa si se denomina
<navigation property name><primary key property name> . Por ejemplo, StudentID para la propiedad de navegación
Student , puesto que la clave principal de la entidad Student es ID . Las propiedades de clave externa también se
pueden denominar <primary key property name> . Por ejemplo CourseID , dado que la clave principal de la entidad
Course es CourseID .
La entidad Course
En la carpeta Models, cree Course.cs con el código siguiente:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
La propiedad Enrollments es una propiedad de navegación. Una entidad Course puede estar relacionada con
cualquier número de entidades Enrollment .
El atributo DatabaseGenerated permite que la aplicación especifique la clave principal en lugar de hacer que la base
de datos la genere.
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
services.AddMvc();
}
El nombre de la cadena de conexión se pasa al contexto mediante una llamada a un método en un objeto
DbContextOptionsBuilder . Para el desarrollo local, el sistema de configuración de ASP.NET Core lee la cadena de
conexión desde el archivo appsettings.json.
Agregue instrucciones usingpara los espacios de nombres ContosoUniversity.Data y
Microsoft.EntityFrameworkCore . Compile el proyecto.
using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
Abra el archivo appsettings.json y agregue una cadena de conexión como se muestra en el código siguiente:
{
"ConnectionStrings": {
"DefaultConnection": "Server=
(localdb)\\mssqllocaldb;Database=ContosoUniversity1;ConnectRetryCount=0;Trusted_Connection=True;MultipleActiveR
esultSets=true"
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
}
}
En la cadena de conexión anterior se usa ConnectRetryCount=0 para evitar que SQLClient se bloquee.
SQL Server Express LocalDB
La cadena de conexión especifica una base de datos de SQL Server LocalDB. LocalDB es una versión ligera del
motor de base de datos de SQL Server Express y está dirigida al desarrollo de aplicaciones, no al uso en
producción. LocalDB se inicia a petición y se ejecuta en modo de usuario, sin necesidad de una configuración
compleja. De forma predeterminada, LocalDB crea archivos de base de datos .mdf en el directorio C:/Users/<user> .
using ContosoUniversity.Models;
using System;
using System.Linq;
namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
context.Database.EnsureCreated();
El código comprueba si hay estudiantes en la base de datos. Si no hay ningún estudiante en la base de datos, se
inicializa con datos de prueba. Carga los datos de prueba en matrices en lugar de colecciones List<T> para
optimizar el rendimiento.
El método EnsureCreated crea automáticamente la base de datos para el contexto de base de datos. Si la base de
datos existe, EnsureCreated vuelve sin modificarla.
En Program.cs, modifique el método Main para que haga lo siguiente:
Obtener una instancia del contexto de base de datos desde el contenedor de inserción de dependencias.
Llamar al método de inicialización, pasándolo al contexto.
Eliminar el contexto cuando el método de inicialización finalice.
En el código siguiente se muestra el archivo Program.cs actualizado.
namespace ContosoUniversity
{
public class Program
{
public static void Main(string[] args)
{
var host = BuildWebHost(args);
host.Run();
}
La primera vez que se ejecuta la aplicación, se crea la base de datos y se inicializa con datos de prueba. Cuando se
actualice el modelo de datos:
Se elimina la base de datos.
Se actualiza el método de inicialización.
Se ejecuta la aplicación y se crea una base de datos inicializada.
En los tutoriales posteriores, la base de datos se actualiza cuando cambia el modelo de datos, sin tener que
eliminarla y volver a crearla.
Install-Package Microsoft.VisualStudio.Web.CodeGeneration.Design
Install-Package Microsoft.VisualStudio.Web.CodeGeneration.Utils
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Utils" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
</ItemGroup>
</Project>
dotnet restore
dotnet tool install --global dotnet-aspnet-codegenerator --version 2.1.0
dotnet aspnet-codegenerator razorpage -m Student -dc SchoolContext -udl -outDir Pages\Students --
referenceScriptLibraries
Si se produce un error:
Abra una ventana de comandos en el directorio del proyecto (el directorio que contiene los archivos Program.cs,
Startup.cs y .csproj).
Compile el proyecto. La compilación genera errores similares a los siguientes:
1>Pages\Students\Index.cshtml.cs(26,38,26,45): error CS1061: 'SchoolContext' does not contain a definition for
'Student'
Cambie globalmente _context.Student por _context.Students (es decir, agregue una "s" a Student ). Se
encuentran y actualizan siete repeticiones. Esperamos solucionar este problema en la próxima versión.
En la tabla siguiente se incluyen los detalles de los parámetros de los generadores de código de ASP.NET Core:
PARÁMETRO DESCRIPTION
Prueba de la aplicación
Ejecute la aplicación y haga clic en el vínculo Students. Según el ancho del explorador, el vínculo Students aparece
en la parte superior de la página. Si el vínculo Students no se ve, haga clic en el icono de navegación en la esquina
superior derecha.
Convenciones
La cantidad de código que se escribe para que EF Core cree una base de datos completa es mínima debido al uso
de convenciones o a las suposiciones que hace EF Core.
Los nombres de las propiedades DbSet se usan como nombres de tabla. Para las entidades a las que no se
hace referencia con una propiedad DbSet , los nombres de clase de entidad se usan como nombres de tabla.
Los nombres de propiedad de entidad se usan para los nombres de columna.
Las propiedades de entidad que se denominan ID o classnameID se reconocen como propiedades de clave
principal.
Una propiedad se interpreta como propiedad de clave externa si se denomina (por ejemplo, StudentID para
la propiedad de navegación Student , dado que la clave principal de la entidad Student es ID ). Las
propiedades de clave externa también se pueden denominar (por ejemplo EnrollmentID , dado que la clave
principal de la entidad Enrollment es EnrollmentID ).
El comportamiento de las convenciones se puede reemplazar. Por ejemplo, los nombres de tabla se pueden
especificar explícitamente, como se muestra anteriormente en este tutorial. Los nombres de columna se pueden
establecer explícitamente. Las claves principales y las claves externas se pueden establecer explícitamente.
Código asincrónico
La programación asincrónica es el modo predeterminado de ASP.NET Core y EF Core.
Un servidor web tiene un número limitado de subprocesos disponibles y, en situaciones de carga alta, es posible
que todos los subprocesos disponibles estén en uso. Cuando esto ocurre, el servidor no puede procesar nuevas
solicitudes hasta que los subprocesos se liberen. Con el código sincrónico, se pueden acumular muchos
subprocesos mientras no estén realizando ningún trabajo porque están a la espera de que finalice la E/S. Con el
código asincrónico, cuando un proceso está a la espera de que finalice la E/S, se libera su subproceso para el que el
servidor lo use para el procesamiento de otras solicitudes. Como resultado, el código asincrónico permite que los
recursos de servidor se usen de forma más eficaz, y el servidor está habilitado para administrar más tráfico sin
retrasos.
El código asincrónico introduce una pequeña cantidad de sobrecarga en tiempo de ejecución. En situaciones de
poco tráfico, la disminución del rendimiento es insignificante, mientras que en situaciones de tráfico elevado, la
posible mejora del rendimiento es importante.
En el código siguiente, la palabra clave async , el valor devuelto Task<T> , la palabra clave await y el método
ToListAsync hacen que el código se ejecute de forma asincrónica.
Un contexto de EF Core no es seguro para subprocesos: no intente realizar varias operaciones en paralelo.
Para aprovechar las ventajas de rendimiento del código asincrónico, compruebe que en los paquetes de
biblioteca (por ejemplo para paginación) se usa async si llaman a métodos de EF Core que envían consultas
a la base de datos.
Para obtener más información sobre la programación asincrónica en .NET, vea Información general de Async.
En el siguiente tutorial, se examinan las operaciones CRUD (crear, leer, actualizar y eliminar) básicas.
S IG U IE N T E
Páginas de Razor con EF Core en ASP.NET Core:
CRUD (2 de 8)
25/06/2018 • 21 minutes to read • Edit Online
FindAsync :
Busca una entidad con la clave principal (PK). Si el contexto realiza el seguimiento de una entidad con la clave
principal, se devuelve sin una solicitud a la base de datos.
Es sencillo y conciso.
Está optimizado para buscar una sola entidad.
Puede tener ventajas de rendimiento en algunas situaciones, pero rara vez entra en juego para escenarios web
normales.
Usa implícitamente FirstAsync en lugar de SingleAsync. Pero si quiere incluir otras entidades, Find ya no
resulta apropiado. Esto significa que puede que necesite descartar Find y cambiar a una consulta cuando la
aplicación progrese.
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
@page "{id:int?}"
Ejecute la aplicación, haga clic en un vínculo Details y compruebe que la dirección URL pasa el identificador como
datos de ruta ( http://localhost:5000/Students/Details/2 ).
No cambie globalmente @page por @page "{id:int}" ; esta acción rompería los vínculos a las páginas Home y
Create.
Agregar datos relacionados
El código con scaffolding de la página Students Index no incluye la propiedad Enrollments . En esta sección, se
mostrará el contenido de la colección Enrollments en la página Details.
El método OnGetAsync de Pages/Students/Details.cshtml.cs usa el método FirstOrDefaultAsync para recuperar
una única entidad Student . Agregue el código resaltado siguiente:
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
if (Student == null)
{
return NotFound();
}
return Page();
}
if (await TryUpdateModelAsync<Student>(
emptyStudent,
"student", // Prefix for form value.
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{
_context.Students.Add(emptyStudent);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
return null;
}
TryUpdateModelAsync
Examine el código de TryUpdateModelAsync:
if (await TryUpdateModelAsync<Student>(
emptyStudent,
"student", // Prefix for form value.
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{
using System;
namespace ContosoUniversity.Models
{
public class StudentVM
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
}
}
Los modelos de vista ofrecen una forma alternativa de evitar la publicación excesiva. El modelo de vista contiene
solo las propiedades que se van a ver (mostrar) o actualizar.
En el código siguiente se usa el modelo de vista StudentVM para crear un alumno:
[BindProperty]
public StudentVM StudentVM { get; set; }
El método SetValues establece los valores de este objeto mediante la lectura de otro objeto PropertyValues.
SetValues usa la coincidencia de nombres de propiedad. No es necesario que el tipo de modelo de vista esté
relacionado con el tipo de modelo, basta con que tenga propiedades que coincidan.
El uso de StudentVM requiere que se actualice CreateVM.cshtml para usar StudentVM en lugar de Student .
En las páginas de Razor, la clase derivada PageModel es el modelo de vista.
[BindProperty]
public Student Student { get; set; }
if (Student == null)
{
return NotFound();
}
return Page();
}
if (await TryUpdateModelAsync<Student>(
studentToUpdate,
"student",
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
return Page();
}
}
Los cambios de código son similares a la página Create con algunas excepciones:
OnPostAsync tiene un parámetro id opcional.
El estudiante actual se obtiene de la base de datos, en lugar de crear un estudiante vacío.
FirstOrDefaultAsync se ha reemplazado con FindAsync. FindAsync es una buena elección cuando se
selecciona una entidad de la clave principal. Vea FindAsync para obtener más información.
Probar las páginas Edit y Create
Cree y modifique algunas entidades Student.
Estados de entidad
El contexto de base de datos realiza el seguimiento de si las entidades en memoria están sincronizadas con sus
filas correspondientes en la base de datos. La información de sincronización del contexto de base de datos
determina qué ocurre cuando se llama a SaveChanges . Por ejemplo, cuando se pasa una nueva entidad al método
Add , el estado de esa entidad se establece en Added . Cuando se llama a SaveChanges , el contexto de base de
datos emite un comando INSERT de SQL.
Una entidad puede estar en uno de los estados siguientes:
Added: la entidad no existe todavía en la base de datos. El método SaveChanges emite una instrucción
INSERT.
Unchanged : no es necesario guardar cambios con esta entidad. Una entidad tiene este estado cuando se lee
desde la base de datos.
Modified : se han modificado algunos o todos los valores de propiedad de la entidad. El método
SaveChanges emite una instrucción UPDATE.
Deleted : la entidad se ha marcado para su eliminación. El método SaveChanges emite una instrucción
DELETE.
Detached : el contexto de base de datos no está realizando el seguimiento de la entidad.
En una aplicación de escritorio, los cambios de estado normalmente se establecen de forma automática. Se lee
una entidad, se realizan cambios y el estado de la entidad se cambia automáticamente a Modified . La llamada a
SaveChanges genera una instrucción UPDATE de SQL que solo actualiza las propiedades modificadas.
En una aplicación web, el DbContext que lee una entidad y muestra los datos se elimina después de representar
una página. Cuando se llama al método OnPostAsync de una página, se realiza una nueva solicitud web con una
instancia nueva de DbContext . Volver a leer la entidad en ese contexto nuevo simula el procesamiento de
escritorio.
[BindProperty]
public Student Student { get; set; }
public string ErrorMessage { get; set; }
if (Student == null)
{
return NotFound();
}
if (saveChangesError.GetValueOrDefault())
{
ErrorMessage = "Delete failed. Try again";
}
return Page();
}
if (student == null)
{
return NotFound();
}
try
{
_context.Students.Remove(student);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
return RedirectToAction("./Delete",
new { id = id, saveChangesError = true });
}
}
En el código anterior se recupera la entidad seleccionada y después se llama al método Remove para establecer el
estado de la entidad en Deleted . Cuando se llama a SaveChanges , se genera un comando DELETE de SQL. Si se
produce un error en Remove :
Se detecta la excepción de base de datos.
Se llama al método OnGetAsync de las páginas Delete con saveChangesError=true .
Actualizar la página de Razor Delete
Agregue el siguiente mensaje de error resaltado a la página de Razor Delete.
@page "{id:int}"
@model ContosoUniversity.Pages.Students.DeleteModel
@{
ViewData["Title"] = "Delete";
}
<h2>Delete</h2>
<p class="text-danger">@Model.ErrorMessage</p>
Pruebe Delete.
Errores comunes
Student/Home u otros vínculos no funcionan:
Compruebe que la página de Razor contiene la directiva @page correcta. Por ejemplo, la página de Razor
Student/Home no debe contener una plantilla de ruta:
@page "{id:int}"
A N T E R IO R S IG U IE N T E
Páginas de Razor con EF Core en ASP.NET Core:
Ordenación, filtrado y paginación (3 de 8)
25/05/2018 • 26 minutes to read • Edit Online
Si experimenta problemas que no puede resolver, descargue la aplicación completada para esta fase.
switch (sortOrder)
{
case "name_desc":
studentIQ = studentIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentIQ = studentIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentIQ = studentIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentIQ = studentIQ.OrderBy(s => s.LastName);
break;
}
El código anterior recibe un parámetro sortOrder de la cadena de consulta en la dirección URL. La aplicación
auxiliar de etiquetas delimitadoras genera la dirección URL (incluida la cadena de consulta).
El parámetro sortOrder es "Name" o "Date". Opcionalmente, el parámetro sortOrder puede ir seguido de
"_desc" para especificar el orden descendente. El criterio de ordenación predeterminado es el ascendente.
Cuando se solicita la página de índice del vínculo Students no hay ninguna cadena de consulta. Los alumnos se
muestran en orden ascendente por apellido. El orden ascendente por apellido es el valor predeterminado (caso de
paso explícito) en la instrucción switch . Cuando el usuario hace clic en un vínculo de encabezado de columna, se
proporciona el valor sortOrder correspondiente en el valor de la cadena de consulta.
La página de Razor usa NameSort y DateSort para configurar los hipervínculos del encabezado de columna con
los valores de cadena de consulta adecuados:
public async Task OnGetAsync(string sortOrder)
{
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
switch (sortOrder)
{
case "name_desc":
studentIQ = studentIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentIQ = studentIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentIQ = studentIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentIQ = studentIQ.OrderBy(s => s.LastName);
break;
}
La primera línea especifica que, cuando sortOrder es NULL o está vacío, NameSort se establece en "name_desc".
Si sortOrder no es NULL ni está vacío, NameSort se establece en una cadena vacía.
El ?: operator también se conoce como el operador ternario.
Estas dos instrucciones habilitan la página para establecer los hipervínculos de encabezado de columna de la
siguiente forma:
El método usa LINQ to Entities para especificar la columna por la que se va a ordenar. El código inicializa un
IQueryable<Student> antes de la instrucción switch y lo modifica en la instrucción switch:
public async Task OnGetAsync(string sortOrder)
{
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
switch (sortOrder)
{
case "name_desc":
studentIQ = studentIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentIQ = studentIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentIQ = studentIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentIQ = studentIQ.OrderBy(s => s.LastName);
break;
}
Cuando se crea o se modifica un IQueryable , no se envía ninguna consulta a la base de datos. La consulta no se
ejecuta hasta que el objeto IQueryable se convierte en una colección. IQueryable se convierte en una colección
mediante una llamada a un método como ToListAsync . Por lo tanto, el código IQueryable produce una única
consulta que no se ejecuta hasta la siguiente instrucción:
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort">
@Html.DisplayNameFor(model => model.Student[0].LastName)
</a>
</th>
<th>
@Html.DisplayNameFor(model => model.Student[0].FirstMidName)
</th>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.DateSort">
@Html.DisplayNameFor(model => model.Student[0].EnrollmentDate)
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Student)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
El código anterior:
Agrega hipervínculos a los encabezados de columna LastName y EnrollmentDate .
Usa la información de NameSort y DateSort para configurar hipervínculos con los valores de criterio de
ordenación actuales.
Para comprobar que la ordenación funciona:
Ejecute la aplicación y haga clic en la pestaña Students.
Haga clic en Last Name.
Haga clic en Enrollment Date.
Para comprender mejor el código:
En Student/Index.cshtml.cs, establezca un punto de interrupción en switch (sortOrder) .
Agregue una inspección para NameSort y DateSort .
En Student/Index.cshtml, establezca un punto de interrupción en
@Html.DisplayNameFor(model => model.Student[0].LastName) .
switch (sortOrder)
{
case "name_desc":
studentIQ = studentIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentIQ = studentIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentIQ = studentIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentIQ = studentIQ.OrderBy(s => s.LastName);
break;
}
El código anterior:
Agrega el parámetro searchString al método OnGetAsync . El valor de la cadena de búsqueda se recibe desde
un cuadro de texto que se agrega en la siguiente sección.
Se agregó una cláusula Where a la instrucción LINQ. La cláusula Where selecciona solo los alumnos cuyo
nombre o apellido contienen la cadena de búsqueda. La instrucción LINQ se ejecuta solo si hay un valor para
buscar.
Nota: El código anterior llama al método Where en un objeto IQueryable y el filtro se procesa en el servidor. En
algunos escenarios, la aplicación puede hacer una llamada al método Where como un método de extensión en
una colección en memoria. Por ejemplo, suponga que _context.Students cambia de DbSet de EF Core a un
método de repositorio que devuelve una colección IEnumerable . Lo más habitual es que el resultado fuera el
mismo, pero en algunos casos puede ser diferente.
Por ejemplo, la implementación de .NET Framework de Contains realiza una comparación que distingue
mayúsculas de minúsculas de forma predeterminada. En SQL Server, la distinción entre mayúsculas y minúsculas
de Contains viene determinada por la configuración de intercalación de la instancia de SQL Server. SQL Server
no diferencia entre mayúsculas y minúsculas de forma predeterminada. Se podría llamar a ToUpper para hacer
explícitamente que la prueba no distinga entre mayúsculas y minúsculas:
Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())
El código anterior garantiza que los resultados no distingan entre mayúsculas y minúsculas si cambia el código
para que use IEnumerable . Cuando se llama a Contains en una colección IEnumerable , se usa la implementación
de .NET Core. Cuando se llama a Contains en un objeto IQueryable , se usa la implementación de la base de
datos. Devolver un IEnumerable desde un repositorio puede acarrear una disminución significativa del
rendimiento:
1. Todas las filas se devuelven desde el servidor de base de datos.
2. El filtro se aplica a todas las filas devueltas en la aplicación.
Hay una disminución del rendimiento por llamar a ToUpper . El código ToUpper agrega una función en la cláusula
WHERE de la instrucción SELECT de TSQL. La función agregada impide que el optimizador use un índice. Dado
que SQL está instalado para no distinguir entre mayúsculas y minúsculas, es mejor evitar llamar a ToUpper
cuando no sea necesario.
Agregar un cuadro de búsqueda a la página de índice de Student
En Pages/Student/Index.cshtml, agregue el siguiente código resaltado para crear un botón Search y cromo
ordenado.
@page
@model ContosoUniversity.Pages.Students.IndexModel
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
El código anterior usa la aplicación auxiliar de etiquetas <form> para agregar el cuadro de texto de búsqueda y el
botón. De forma predeterminada, la aplicación auxiliar de etiquetas <form> envía datos de formulario con POST.
Con POST, los parámetros se pasan en el cuerpo del mensaje HTTP y no en la dirección URL. Cuando se usa el
método HTTP GET, los datos del formulario se pasan en la dirección URL como cadenas de consulta. Pasar los
datos con cadenas de consulta permite a los usuarios marcar la dirección URL. Las directrices de W3C
recomiendan el uso de GET cuando la acción no produzca ninguna actualización.
Pruebe la aplicación:
Seleccione la pestaña Students y escriba una cadena de búsqueda.
Seleccione Search.
Fíjese en que la dirección URL contiene la cadena de búsqueda.
http://localhost:5000/Students?SearchString=an
Si se colocó un marcador en la página, el marcador contiene la dirección URL a la página y la cadena de consulta
de SearchString . El method="get" en la etiqueta form es lo que ha provocado que se generara la cadena de
consulta.
Actualmente, cuando se selecciona un vínculo de ordenación del encabezado de columna, el filtro de valor del
cuadro Search se pierde. El valor de filtro perdido se fija en la sección siguiente.
namespace ContosoUniversity
{
public class PaginatedList<T> : List<T>
{
public int PageIndex { get; private set; }
public int TotalPages { get; private set; }
this.AddRange(items);
}
El método CreateAsync en el código anterior toma el tamaño y el número de la página, y aplica las instrucciones
Skip y Take correspondientes a IQueryable . Cuando ToListAsync se llama en IQueryable , devuelve una lista
que solo contiene la página solicitada. Las propiedades HasPreviousPage y HasNextPage se usan para habilitar o
deshabilitar los botones de página Previous y Next.
El método CreateAsync se usa para crear la PaginatedList<T> . No se puede crear un constructor del objeto
PaginatedList<T> , los constructores no pueden ejecutar código asincrónico.
CurrentFilter = searchString;
int pageSize = 3;
Student = await PaginatedList<Student>.CreateAsync(
studentIQ.AsNoTracking(), pageIndex ?? 1, pageSize);
}
El código anterior agrega el índice de la página, el sortOrder actual y el currentFilter a la firma del método.
if (searchString != null)
{
pageIndex = 1;
}
else
{
searchString = currentFilter;
}
El método PaginatedList.CreateAsync convierte la consulta del alumno en una sola página de alumnos de un tipo
de colección que admita la paginación. Esa única página de alumnos se pasa a la página de Razor.
@page
@model ContosoUniversity.Pages.Students.IndexModel
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
@{
var prevDisabled = !Model.Student.HasPreviousPage ? "disabled" : "";
var nextDisabled = !Model.Student.HasNextPage ? "disabled" : "";
}
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Student.PageIndex - 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-default @prevDisabled">
Previous
</a>
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Student.PageIndex + 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-default @nextDisabled">
Next
</a>
Los vínculos del encabezado de la columna usan la cadena de consulta para pasar la cadena de búsqueda actual al
método OnGetAsync , de modo que el usuario pueda ordenar los resultados del filtro:
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model => model.Student[0].LastName)
</a>
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Student.PageIndex - 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-default @prevDisabled">
Previous
</a>
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Student.PageIndex + 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-default @nextDisabled">
Next
</a>
using System;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models.SchoolViewModels
{
public class EnrollmentDateGroup
{
[DataType(DataType.Date)]
public DateTime? EnrollmentDate { get; set; }
namespace ContosoUniversity.Pages
{
public class AboutModel : PageModel
{
private readonly SchoolContext _context;
La instrucción LINQ agrupa las entidades de alumnos por fecha de inscripción, calcula la cantidad de entidades
que se incluyen en cada grupo y almacena los resultados en una colección de objetos de modelo de la vista
EnrollmentDateGroup .
Nota: EF Core actualmente no admite el comando group de LINQ. En el código anterior, se devuelven todos los
registros de alumnos desde SQL Server. El comando group se aplica en la aplicación de las páginas de Razor, no
en SQL Server. EF Core 2.1 admitirá este operador group de LINQ y la agrupación se produce en SQL Server.
Vea Relational: Support translating GroupBy() to SQL (Relacional: compatibilidad para traducir GroupBy() para
SQL ). EF Core 2.1 se publicará con .NET Core 2.1. Para obtener más información, vea la página Mapa de ruta de
.NET Core.
Modificar la página de Razor About
Reemplace el código del archivo Pages/About.cshtml por el código siguiente:
@page
@model ContosoUniversity.Pages.AboutModel
@{
ViewData["Title"] = "Student Body Statistics";
}
<table>
<tr>
<th>
Enrollment Date
</th>
<th>
Students
</th>
</tr>
Ejecute la aplicación y vaya a la página About. En una tabla se muestra el número de alumnos para cada fecha de
inscripción.
Si experimenta problemas que no puede resolver, descargue la aplicación completada para esta fase.
Recursos adicionales
Depuración del código fuente de ASP.NET Core 2.x
En el tutorial siguiente, la aplicación usa las migraciones para actualizar el modelo de datos.
A N T E R IO R S IG U IE N T E
Páginas de Razor con EF Core en ASP.NET Core:
Migraciones (4 de 8)
25/06/2018 • 17 minutes to read • Edit Online
Los números de versión en el ejemplo anterior eran los actuales cuando se escribió el tutorial. Use la misma
versión para las herramientas de la CLI de EF Core disponibles en los otros paquetes.
{
"ConnectionStrings": {
"DefaultConnection": "Server=
(localdb)\\mssqllocaldb;Database=ContosoUniversity2;ConnectRetryCount=0;Trusted_Connection=True;MultipleActiv
eResultSets=true"
},
Cambiar el nombre de la base de datos en la cadena de conexión hace que la primera migración cree una base de
datos. Se crea una base de datos porque no existe ninguna con ese nombre. No es necesario cambiar la cadena
de conexión para comenzar a usar las migraciones.
Una alternativa a cambiar el nombre de la base de datos es eliminarla. Use el Explorador de objetos de SQL
Server (SSOX) o el comando de la CLI database drop :
Si se produce un error en la migración y se muestra el mensaje "No se puede tener acceso al archivo...
ContosoUniversity.dll ya que otro proceso lo está usando." :
Detenga IIS Express.
Salga y reinicie Visual Studio o
Busque el icono de IIS Express en la bandeja del sistema de Windows.
Haga clic con el botón derecho en el icono de IIS Express y, después, haga clic en ContosoUniversity
> Detener sitio.
Si se muestra el mensaje de error "Error de complicación.", vuelva a ejecutar el comando. Si obtiene este error,
deje una nota en la parte inferior de este tutorial.
Examinar los métodos Up y Down
El comando de EF Core migrations add generó código desde donde crear la base de datos. Este código de
migraciones se encuentra en el archivo Migrations<marca_de_tiempo>_InitialCreate.cs. El método Up de la
clase InitialCreate crea las tablas de base de datos que corresponden a los conjuntos de entidades del modelo
de datos. El método Down las elimina, tal como se muestra en el ejemplo siguiente:
migrationBuilder.DropTable(
name: "Course");
migrationBuilder.DropTable(
name: "Student");
}
}
}
Las migraciones llaman al método Up para implementar los cambios del modelo de datos para una migración.
Cuando se escribe un comando para revertir la actualización, las migraciones llaman al método Down .
El código anterior es para la migración inicial. Ese código se creó cuando se ejecutó el comando
migrations add InitialCreate . El parámetro de nombre de la migración ("InitialCreate" en el ejemplo) se usa para
el nombre de archivo. El nombre de la migración puede ser cualquier nombre de archivo válido. Es más
recomendable elegir una palabra o frase que resuma lo que se hace en la migración. Por ejemplo, una migración
que ha agregado una tabla de departamento podría denominarse "AddDepartmentTable".
Si la migración inicial está creada y la base de datos existe:
Se genera el código de creación de la base de datos.
El código de creación de la base de datos no tiene que ejecutarse porque la base de datos ya coincide con el
modelo de datos. Si el código de creación de la base de datos se está ejecutando, no hace ningún cambio
porque la base de datos ya coincide con el modelo de datos.
Cuando la aplicación se implementa en un entorno nuevo, se debe ejecutar el código de creación de la base de
datos para crear la base de datos.
Anteriormente, la cadena de conexión se cambió para usar un nombre nuevo para la base de datos. La base de
datos especificada no existe, por lo que las migraciones crean la base de datos.
La instantánea del modelo de datos
Las migraciones crean una instantánea del esquema de la base de datos actual en
Migrations/SchoolContextModelSnapshot.cs. Cuando se agrega una migración, EF determina qué ha cambiado
mediante la comparación del modelo de datos con el archivo de instantánea.
Cuando elimine una migración, use el comando dotnet ef migrations remove. dotnet ef migrations remove
elimina la migración y garantiza que la instantánea se restablece correctamente.
Vea Migraciones en entornos de equipo para más información sobre cómo se usa el archivo de instantánea.
Quitar EnsureCreated
Para el desarrollo inicial se usó el comando EnsureCreated . En este tutorial, se usan las migraciones.
EnsureCreated tiene las siguientes limitaciones:
context.Database.EnsureCreated();
info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0]
User profile is available. Using 'C:\Users\username\AppData\Local\ASP.NET\DataProtection-Keys' as key
repository and Windows DPAPI to encrypt keys at rest.
info: Microsoft.EntityFrameworkCore.Infrastructure[100403]
Entity Framework Core 2.0.0-rtm-26452 initialized 'SchoolContext' using provider
'Microsoft.EntityFrameworkCore.SqlServer' with options: None
info: Microsoft.EntityFrameworkCore.Database.Command[200101]
Executed DbCommand (467ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
CREATE DATABASE [ContosoUniversity2];
info: Microsoft.EntityFrameworkCore.Database.Command[200101]
Executed DbCommand (20ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE [__EFMigrationsHistory] (
[MigrationId] nvarchar(150) NOT NULL,
[ProductVersion] nvarchar(32) NOT NULL,
CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId])
);
info: Microsoft.EntityFrameworkCore.Database.Command[200101]
Executed DbCommand (3ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20170816151242_InitialCreate', N'2.0.0-rtm-26452');
Done.
Para reducir el nivel de detalle en los mensajes de registro, cámbielo en el archivo appsettings.Development.json.
Para obtener más información, vea Introducción al registro.
Use el Explorador de objetos de SQL Server para inspeccionar la base de datos. Observe la adición de una
tabla __EFMigrationsHistory . La tabla __EFMigrationsHistory realiza un seguimiento de las migraciones que se
han aplicado a la base de datos. Examine los datos de la tabla __EFMigrationsHistory , muestra una fila para la
primera migración. En el último registro del ejemplo de salida de la CLI anterior se muestra la instrucción
INSERT que crea esta fila.
Ejecute la aplicación y compruebe que todo funciona correctamente.
EF Core usa la tabla __MigrationsHistory para ver si es necesario ejecutar las migraciones. Si la base de datos
está actualizada, no se ejecuta la migración.
Para obtener más información sobre los comandos de la CLI, vea CLI de .NET Core.
Para obtener más información sobre los comandos de la PMC, vea Consola del Administrador de paquetes
(Visual Studio).
Solución de problemas
Descargue la aplicación completa de esta fase.
La aplicación genera la siguiente excepción:
A N T E R IO R S IG U IE N T E
Páginas de Razor con EF Core en ASP.NET Core:
Modelo de datos (5 de 8)
25/06/2018 • 48 minutes to read • Edit Online
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }
El atributo DataType especifica un tipo de datos más específico que el tipo intrínseco de base de datos. En este
caso solo se debe mostrar la fecha, no la fecha y hora. La enumeración DataType proporciona muchos tipos de
datos, como Date (Fecha), Time (Hora), PhoneNumber (Número de teléfono), Currency (Divisa), EmailAddress
(Dirección de correo electrónico), etc. El atributo DataType también puede permitir que la aplicación proporcione
automáticamente características específicas del tipo. Por ejemplo:
El vínculo mailto: se crea automáticamente para DataType.EmailAddress .
El selector de fecha se proporciona para DataType.Date en la mayoría de los exploradores.
El atributo DataType emite atributos HTML 5 data- (se pronuncia "datos dash") para su uso por parte de los
exploradores HTML 5. Los atributos DataType no proporcionan validación.
DataType.Date no especifica el formato de la fecha que se muestra. De manera predeterminada, el campo de
fecha se muestra según los formatos predeterminados basados en el elemento CultureInfo del servidor.
El atributo DisplayFormat se usa para especificar el formato de fecha de forma explícita:
La configuración ApplyFormatInEditMode especifica que el formato también debe aplicarse a la interfaz de usuario
de edición. Algunos campos no deben usar ApplyFormatInEditMode . Por ejemplo, el símbolo de divisa
generalmente no debe mostrarse en un cuadro de texto de edición.
El atributo DisplayFormat puede usarse por sí solo. Normalmente se recomienda usar el atributo DataType con el
atributo DisplayFormat . El atributo DataType transmite la semántica de los datos en lugar de cómo se
representan en una pantalla. El atributo DataType proporciona las siguientes ventajas que no están disponibles
en DisplayFormat :
El explorador puede habilitar características de HTML5. Por ejemplo, mostrar un control de calendario, el
símbolo de divisa adecuado según la configuración regional, vínculos de correo electrónico, validación de
entradas del lado cliente, etc.
De manera predeterminada, el explorador representa los datos con el formato correcto según la configuración
regional.
Para obtener más información, vea la documentación de la aplicación auxiliar de etiquetas <entrada>.
Ejecute la aplicación. Vaya a la página de índice de Students. Ya no se muestran las horas. Todas las vistas que usa
el modelo Student muestran la fecha sin hora.
El atributo StringLength
Las reglas de validación de datos y los mensajes de error de validación se pueden especificar con atributos. El
atributo StringLength especifica la longitud mínima y máxima de caracteres que se permite en un campo de
datos. El atributo StringLength también proporciona validación del lado cliente y del lado servidor. El valor
mínimo no influye en el esquema de base de datos.
Actualice el modelo Student con el código siguiente:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[StringLength(50)]
public string LastName { get; set; }
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }
El código anterior limita los nombres a no más de 50 caracteres. El atributo StringLength no impide que un
usuario escriba un espacio en blanco para un nombre. El atributo RegularExpression se usa para aplicar
restricciones a la entrada. Por ejemplo, el código siguiente requiere que el primer carácter sea una letra
mayúscula y el resto de caracteres sean alfabéticos:
[RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")]
Ejecute la aplicación:
Vaya a la página Students.
Seleccione Create New y escriba un nombre más de 50 caracteres.
Seleccione Create, la validación del lado cliente muestra un mensaje de error.
En el Explorador de objetos de SQL Server, (SSOX) abra el diseñador de tablas de Student haciendo doble clic
en la tabla Student.
La imagen anterior muestra el esquema para la tabla Student . Los campos de nombre tienen tipo nvarchar(MAX)
porque las migraciones no se han ejecutado en la base de datos. Cuando se ejecutan las migraciones más
adelante en este tutorial, los campos de nombre se convierten en nvarchar(50) .
El atributo Column
Los atributos pueden controlar cómo se asignan las clases y propiedades a la base de datos. En esta sección, el
atributo Column se usa para asignar el nombre de la propiedad FirstMidName a "FirstName" en la base de datos.
Cuando se crea la base de datos, los nombres de propiedad en el modelo se usan para los nombres de columna
(excepto cuando se usa el atributo Column ).
El modelo Student usa FirstMidName para el nombre de campo por la posibilidad de que el campo contenga
también un segundo nombre.
Actualice el archivo Models/Student.cs con el siguiente código resaltado:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[StringLength(50)]
public string LastName { get; set; }
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
[Column("FirstName")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }
La adición del atributo Column cambia el modelo de respaldo de SchoolContext . El modelo que está haciendo la
copia de seguridad de SchoolContext ya no coincide con la base de datos. Si la aplicación se ejecuta antes de
aplicar las migraciones, se genera la siguiente excepción:
La advertencia se genera porque los campos de nombre ahora están limitados a 50 caracteres. Si un nombre en
la base de datos tenía más de 50 caracteres, se perderían desde el 51 hasta el último carácter.
Pruebe la aplicación.
Abra la tabla de estudiantes en SSOX:
Antes de aplicar la migración, las columnas de nombre eran de tipo nvarchar(MAX). Las columnas de nombre
ahora son nvarchar(50) . El nombre de columna ha cambiado de FirstMidName a FirstName .
NOTE
En la sección siguiente, la creación de la aplicación en algunas de las fases genera errores del compilador. Las instrucciones
especifican cuándo se debe compilar la aplicación.
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
[Column("FirstName")]
[Display(Name = "First Name")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Enrollment Date")]
public DateTime EnrollmentDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}
El atributo Required
El atributo Required hace que las propiedades de nombre sean campos obligatorios. El atributo Required no es
necesario para los tipos que no aceptan valores NULL, como los tipos de valor ( DateTime , int , double , etc.). Los
tipos que no aceptan valores NULL se tratan automáticamente como campos obligatorios.
El atributo Required se podría reemplazar con un parámetro de longitud mínima en el atributo StringLength :
El atributo Display
El atributo Display especifica que el título de los cuadros de texto debe ser "First Name", "Last Name", "Full
Name" y "Enrollment Date". Los títulos predeterminados no tenían ningún espacio de división de palabras, por
ejemplo "Lastname".
La propiedad calculada FullName
FullName es una propiedad calculada que devuelve un valor que se crea mediante la concatenación de otras dos
propiedades. No se puede establecer FullName , tiene solo un descriptor de acceso get. No se crea ninguna
columna FullName en la base de datos.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Instructor
{
public int ID { get; set; }
[Required]
[Display(Name = "Last Name")]
[StringLength(50)]
public string LastName { get; set; }
[Required]
[Column("FirstName")]
[Display(Name = "First Name")]
[StringLength(50)]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Hire Date")]
public DateTime HireDate { get; set; }
Tenga en cuenta que varias propiedades son las mismas en las entidades Student y Instructor . En el tutorial
Implementación de la herencia, más adelante en esta serie, se refactoriza este código para eliminar la
redundancia.
En una sola línea puede haber varios atributos. Los atributos HireDate pudieron escribirse de la manera
siguiente:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class OfficeAssignment
{
[Key]
public int InstructorID { get; set; }
[StringLength(50)]
[Display(Name = "Office Location")]
public string Location { get; set; }
El atributo Key
El atributo [Key] se usa para identificar una propiedad como la clave principal (PK) cuando el nombre de
propiedad es diferente de classnameID o ID.
Hay una relación de uno a cero o uno entre las entidades Instructor y OfficeAssignment . Solo existe una
asignación de oficina en relación con el instructor a la que está asignada. La clave principal de OfficeAssignment
también es la clave externa (FK) para la entidad Instructor . EF Core no reconoce automáticamente
InstructorID como la clave principal de OfficeAssignment porque:
Por tanto, se usa el atributo Key para identificar InstructorID como la clave principal:
[Key]
public int InstructorID { get; set; }
De forma predeterminada, EF Core trata la clave como no generada por la base de datos porque la columna es
para una relación de identificación.
La propiedad de navegación Instructor
La propiedad de navegación OfficeAssignment para la entidad Instructor acepta valores NULL porque:
Los tipos de referencia, como las clases, aceptan valores NULL.
Un instructor podría no tener una asignación de oficina.
La entidad OfficeAssignment tiene una propiedad de navegación Instructor que no acepta valores NULL
porque:
InstructorIDno acepta valores NULL.
Una asignación de oficina no puede existir sin un instructor.
Cuando una entidad Instructor tiene una entidad OfficeAssignment relacionada, cada entidad tiene una
referencia a la otra en su propiedad de navegación.
El atributo [Required] puede aplicarse a la propiedad de navegación Instructor :
[Required]
public Instructor Instructor { get; set; }
El código anterior especifica que debe haber un instructor relacionado. El código anterior no es necesario porque
la clave externa InstructorID , que también es la clave principal, no acepta valores NULL.
namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
[Range(0, 5)]
public int Credits { get; set; }
La entidad Course tiene una propiedad de clave externa (FK) DepartmentID . DepartmentID apunta a la entidad
relacionada Department . La entidad Course tiene una propiedad de navegación Department .
EF Core no requiere una propiedad de clave externa para un modelo de datos cuando el modelo tiene una
propiedad de navegación para una entidad relacionada.
EF Core crea automáticamente claves externas en la base de datos siempre que se necesiten. EF Core crea
propiedades paralelas para las claves externas creadas automáticamente. Tener la clave externa en el modelo de
datos puede hacer que las actualizaciones sean más sencillas y eficaces. Por ejemplo, considere la posibilidad de
un modelo donde la propiedad de la clave externa DepartmentID no está incluida. Cuando se captura una entidad
de curso para editar:
La entidad Department es NULL si no se carga explícitamente.
Para actualizar la entidad Course, la entidad Department debe capturarse en primer lugar.
Cuando se incluye la propiedad de clave externa DepartmentID en el modelo de datos, no es necesario capturar la
entidad Department antes de una actualización.
El atributo DatabaseGenerated
El atributo [DatabaseGenerated(DatabaseGeneratedOption.None)] especifica que la aplicación proporciona la clave
principal, en lugar de generarla la base de datos.
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
De forma predeterminada, EF Core da por supuesto que la base de datos genera valores de clave principal. Los
valores de clave principal generados por la base de datos suelen ser el mejor método. Para las entidades Course ,
el usuario especifica la clave principal. Por ejemplo, un número de curso como una serie de 1000 para el
departamento de matemáticas, una serie de 2000 para el departamento de inglés.
También se puede usar el atributo DatabaseGenerated para generar valores predeterminados. Por ejemplo, la base
de datos puede generar automáticamente un campo de fecha para registrar la fecha en que se crea o actualiza
una fila. Para obtener más información, vea Propiedades generadas.
Propiedades de clave externa y de navegación
Las propiedades de clave externa (FK) y las de navegación de la entidad Course reflejan las relaciones siguientes:
Un curso se asigna a un departamento, por lo que hay una clave externa DepartmentID y una propiedad de
navegación Department .
Un curso puede tener cualquier número de alumnos inscritos en él, por lo que la propiedad de navegación
Enrollments es una colección:
Un curso puede ser impartido por varios instructores, por lo que la propiedad de navegación CourseAssignments
es una colección:
namespace ContosoUniversity.Models
{
public class Department
{
public int DepartmentID { get; set; }
[DataType(DataType.Currency)]
[Column(TypeName = "money")]
public decimal Budget { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Start Date")]
public DateTime StartDate { get; set; }
El atributo Column
Anteriormente se usó el atributo Column para cambiar la asignación de nombres de columna. En el código de la
entidad Department , se usó el atributo Column para cambiar la asignación de tipos de datos de SQL. La columna
Budget se define mediante el tipo money de SQL Server en la base de datos:
[Column(TypeName="money")]
public decimal Budget { get; set; }
Por lo general, la asignación de columnas no es necesaria. EF Core generalmente elige el tipo de datos de SQL
Server apropiado en función del tipo CLR para la propiedad. El tipo CLR decimal se asigna a un tipo decimal de
SQL Server. Budget es para la divisa, y el tipo de datos money es más adecuado para la divisa.
Propiedades de clave externa y de navegación
Las propiedades de clave externa y de navegación reflejan las relaciones siguientes:
Un departamento puede tener o no un administrador.
Un administrador siempre es un instructor. Por lo tanto, la propiedad InstructorID se incluye como la clave
externa para la entidad Instructor .
La propiedad de navegación se denomina Administrator pero contiene una entidad Instructor :
El signo de interrogación (?) en el código anterior especifica que la propiedad acepta valores NULL.
Un departamento puede tener varios cursos, por lo que hay una propiedad de navegación Courses:
public ICollection<Course> Courses { get; set; }
Nota: Por convención, EF Core permite la eliminación en cascada para las claves externas que no aceptan valores
NULL y para las relaciones de varios a varios. La eliminación en cascada puede dar lugar a reglas de eliminación
en cascada circular. Las reglas de eliminación en cascada circular provocan una excepción cuando se agrega una
migración.
Por ejemplo, si no se ha definido que la propiedad Department.InstructorID acepta valores NULL:
EF Core configura una regla de eliminación en cascada para eliminar el instructor cuando se elimine el
departamento.
Eliminar el instructor cuando se elimine el departamento no es el comportamiento previsto.
Si las reglas de negocios requieren que la propiedad InstructorID no acepte valores NULL, use la siguiente
instrucción de API fluida:
modelBuilder.Entity<Department>()
.HasOne(d => d.Administrator)
.WithMany()
.OnDelete(DeleteBehavior.Restrict)
namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}
Un registro de inscripción es para un alumno, por lo que hay una propiedad de clave externa StudentID y una
propiedad de navegación Student :
La entidad CourseAssignment
namespace ContosoUniversity.Models
{
public class CourseAssignment
{
public int InstructorID { get; set; }
public int CourseID { get; set; }
public Instructor Instructor { get; set; }
public Course Course { get; set; }
}
}
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
modelBuilder.Entity<CourseAssignment>()
.HasKey(c => new { c.CourseID, c.InstructorID });
}
}
}
El código anterior agrega las nuevas entidades y configura la clave principal compuesta de la entidad
CourseAssignment .
Alternativa de la API fluida a los atributos
El método OnModelCreating del código anterior usa la API fluida para configurar el comportamiento de EF Core.
La API se denomina "fluida" porque a menudo se usa para encadenar una serie de llamadas de método en una
única instrucción. El código siguiente es un ejemplo de la API fluida:
En este tutorial, la API fluida se usa solo para la asignación de base de datos que no se puede realizar con
atributos. Pero la API fluida puede especificar casi todas las reglas de formato, validación y asignación que se
pueden realizar mediante el uso de atributos.
Algunos atributos como MinimumLength no se pueden aplicar con la API fluida. MinimumLength no cambia el
esquema, solo aplica una regla de validación de longitud mínima.
Algunos desarrolladores prefieren usar la API fluida exclusivamente para mantener "limpias" las clases de
entidad. Se pueden mezclar atributos y la API fluida. Hay algunas configuraciones que solo se pueden realizar con
la API fluida (especificando una clave principal compuesta). Hay algunas configuraciones que solo se pueden
realizar con atributos ( MinimumLength ). La práctica recomendada para el uso de atributos o API fluida:
Elija uno de estos dos enfoques.
Use el enfoque elegido de forma tan coherente como sea posible.
Algunos de los atributos utilizados en este tutorial se usan para:
Solo validación (por ejemplo, MinimumLength ).
Solo configuración de EF Core (por ejemplo, HasKey ).
Validación y configuración de EF Core (por ejemplo, [StringLength(50)] ).
Para obtener más información sobre la diferencia entre los atributos y la API fluida, vea Métodos de
configuración.
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using ContosoUniversity.Models;
namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
//context.Database.EnsureCreated();
// Look for any students.
if (context.Students.Any())
{
return; // DB has been seeded
}
El código anterior proporciona datos de inicialización para las nuevas entidades. La mayor parte de este código
crea objetos de entidad y carga los datos de ejemplo. Los datos de ejemplo se usan para pruebas. El código
anterior crea las siguientes relaciones de varios a varios:
Enrollments
CourseAssignment
The ALTER TABLE statement conflicted with the FOREIGN KEY constraint
"FK_dbo.Course_dbo.Department_DepartmentID". The conflict occurred in
database "ContosoUniversity", table "dbo.Department", column 'DepartmentID'.
Cuando se ejecutan migraciones con datos existentes, puede haber restricciones de clave externa que no se
cumplen con los datos existentes. Para este tutorial, se crea una base de datos, por lo que no hay ninguna
infracción de restricciones de clave externa. Vea la sección Corregir las restricciones de claves externas con datos
heredados para obtener instrucciones sobre cómo corregir las infracciones de clave externa en la base de datos
actual.
{
"ConnectionStrings": {
"DefaultConnection": "Server=
(localdb)\\mssqllocaldb;Database=ContosoUniversity3;Trusted_Connection=True;MultipleActiveResultSets=tr
ue"
},
migrationBuilder.AddColumn<int>(
name: "DepartmentID",
table: "Course",
type: "int",
nullable: false,
defaultValue: 0);
El código anterior agrega una clave externa DepartmentID que acepta valores NULL a la tabla Course . La base de
datos del tutorial anterior contiene filas en Course , por lo que no se puede actualizar esa tabla mediante
migraciones.
Para realizar la migración de ComplexDataModel , trabaje con los datos existentes:
Cambie el código para asignar a la nueva columna ( DepartmentID ) un valor predeterminado.
Cree un departamento falso denominado "Temp" para que actúe como el departamento predeterminado.
Corregir las restricciones de clave externa
Actualice el método Up de las clases ComplexDataModel :
Abra el archivo {marca_de_tiempo }_ComplexDataModel.cs.
Convierta en comentario la línea de código que agrega la columna DepartmentID a la tabla Course .
migrationBuilder.AlterColumn<string>(
name: "Title",
table: "Course",
maxLength: 50,
nullable: true,
oldClrType: typeof(string),
oldNullable: true);
//migrationBuilder.AddColumn<int>(
// name: "DepartmentID",
// table: "Course",
// nullable: false,
// defaultValue: 0);
Agregue el código resaltado siguiente. El nuevo código va después del bloque .CreateTable( name: "Department" :
[!code-csharp]
Con los cambios anteriores, las filas Course existentes estarán relacionadas con el departamento "Temp" después
de ejecutar el método ComplexDataModel de Up .
Una aplicación de producción debería:
Incluir código o scripts para agregar filas de Department y filas de Course relacionadas a las nuevas filas de
Department .
No use el departamento "Temp" o el valor predeterminado de Course.DepartmentID .
El siguiente tutorial trata los datos relacionados.
A N T E R IO R S IG U IE N T E
Páginas de Razor con EF Core en ASP.NET Core:
Lectura de datos relacionados (6 de 8)
19/06/2018 • 26 minutes to read • Edit Online
La carga diligente envía varias consultas cuando se incluye una propiedad de navegación de colección:
Una consulta para la consulta principal
Una consulta para cada colección "perimetral" en el árbol de la carga.
Separe las consultas con Load : los datos se pueden recuperar en distintas consultas y EF Core "corrige" las
propiedades de navegación. "Corregir" significa que EF Core rellena automáticamente las propiedades de
navegación. Separar las consultas con Load es más parecido a la carga explícita que a la carga diligente.
Nota: EF Core corrige automáticamente las propiedades de navegación para todas las entidades que se
cargaron previamente en la instancia del contexto. Incluso si los datos de una propiedad de navegación no
se incluyen explícitamente, es posible que la propiedad se siga rellenando si algunas o todas las entidades
relacionadas se cargaron previamente.
Carga explícita. Cuando la entidad se lee por primera vez, no se recuperan datos relacionados. Se debe
escribir código para recuperar los datos relacionados cuando sea necesario. La carga explícita con
consultas independientes da como resultado varias consultas que se envían a la base de datos. Con la
carga explícita, el código especifica las propiedades de navegación que se van a cargar. Use el método
Load para realizar la carga explícita. Por ejemplo:
Carga diferida. Actualmente EF Core no admite la carga diferida. Cuando la entidad se lee por primera vez,
no se recuperan datos relacionados. La primera vez que se obtiene acceso a una propiedad de navegación,
se recuperan automáticamente los datos necesarios para esa propiedad de navegación. Cada vez que se
obtiene acceso a una propiedad de navegación, se envía una consulta a la base de datos.
El operador Select solo carga los datos relacionados necesarios.
El comando anterior aplica scaffolding al modelo Course . Abra el proyecto en Visual Studio.
Compile el proyecto. La compilación genera errores similares a los siguientes:
1>Pages/Courses/Index.cshtml.cs(26,37,26,43): error CS1061: 'SchoolContext' does not contain a definition for
'Course' and no extension method 'Course' accepting a first argument of type 'SchoolContext' could be found
(are you missing a using directive or an assembly reference?)
Cambie globalmente _context.Course por _context.Courses (es decir, agregue una "s" a Course ). Se encuentran
y actualizan siete repeticiones.
Abra Pages/Courses/Index.cshtml.cs y examine el método OnGetAsync . El motor de scaffolding especificado
realiza la carga diligente de la propiedad de navegación Department . El método Include especifica la carga
diligente.
Ejecute la aplicación y haga clic en el vínculo Courses. En la columna Department se muestra el DepartmentID , lo
que no resulta útil.
Actualice el método OnGetAsync con el código siguiente:
El código anterior agrega AsNoTracking . AsNoTracking mejora el rendimiento porque no se realiza el seguimiento
de las entidades devueltas. No se realiza el seguimiento de las entidades porque no se actualizan en el contexto
actual.
Actualice Pages/Courses/Index.cshtml con el marcado resaltado siguiente:
@page
@model ContosoUniversity.Pages.Courses.IndexModel
@{
ViewData["Title"] = "Courses";
}
<h2>Courses</h2>
<p>
<a asp-page="TestCreate">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Course[0].CourseID)
</th>
<th>
@Html.DisplayNameFor(model => model.Course[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Course[0].Credits)
</th>
<th>
@Html.DisplayNameFor(model => model.Course[0].Department)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Course)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.CourseID)
</td>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Credits)
</td>
<td>
@Html.DisplayFor(modelItem => item.Department.Name)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.CourseID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.CourseID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.CourseID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Ejecute la aplicación y haga clic en la pestaña Courses para ver la lista con los nombres de departamento.
El operador Select solo carga los datos relacionados necesarios. Para elementos individuales, como el
Department.Name , se usa INNER JOIN de SQL. En las colecciones, se usa otro acceso de base de datos, como
también hace el operador Include en las colecciones.
En el código siguiente se cargan los datos relacionados con el método Select :
El CourseViewModel :
public class CourseViewModel
{
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public string DepartmentName { get; set; }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ContosoUniversity.Models.SchoolViewModels
{
public class InstructorIndexData
{
public IEnumerable<Instructor> Instructors { get; set; }
public IEnumerable<Course> Courses { get; set; }
public IEnumerable<Enrollment> Enrollments { get; set; }
}
}
El comando anterior aplica scaffolding al modelo Instructor . Abra el proyecto en Visual Studio.
Compile el proyecto. La compilación genera errores.
Cambie globalmente _context.Instructor por _context.Instructors (es decir, agregue una "s" a Instructor ). Se
encuentran y actualizan siete repeticiones.
Ejecute la aplicación y vaya a la página de instructores.
Reemplace Pages/Instructors/Index.cshtml.cs con el código siguiente:
using ContosoUniversity.Models;
using ContosoUniversity.Models.SchoolViewModels; // Add VM
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;
namespace ContosoUniversity.Pages.Instructors
{
public class IndexModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
if (id != null)
{
InstructorID = id.Value;
}
}
}
}
El método OnGetAsync acepta datos de ruta opcionales para el identificador del instructor seleccionado.
Examine la consulta en la página Pages/Instructors/Index.cshtml:
@{
ViewData["Title"] = "Instructors";
}
<h2>Instructors</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>Last Name</th>
<th>First Name</th>
<th>Hire Date</th>
<th>Office</th>
<th>Courses</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Instructor.Instructors)
{
string selectedRow = "";
if (item.ID == Model.InstructorID)
{
selectedRow = "success";
}
<tr class="@selectedRow">
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.HireDate)
</td>
<td>
@if (item.OfficeAssignment != null)
{
@item.OfficeAssignment.Location
}
</td>
<td>
@{
foreach (var course in item.CourseAssignments)
{
@course.Course.CourseID @: @course.Course.Title <br />
}
}
</td>
<td>
<a asp-page="./Index" asp-route-id="@item.ID">Select</a> |
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
En el marcado anterior se realizan los cambios siguientes:
Se actualiza la directiva page de @page a @page "{id:int?}" . "{id:int?}" es una plantilla de ruta. La
plantilla de ruta cambia las cadenas de consulta enteras de la dirección URL por datos de ruta. Por ejemplo,
al hacer clic en el vínculo Select de un instructor con únicamente la directiva @page , se genera una
dirección URL similar a la siguiente:
http://localhost:1234/Instructors?id=2
Se ha agregado una columna Courses en la que se muestran los cursos que imparte cada instructor. Vea
Transición de línea explícita con @: para obtener más información sobre esta sintaxis de Razor.
Ha agregado código que agrega dinámicamente class="success" al elemento tr del instructor
seleccionado. Esto establece el color de fondo de la fila seleccionada mediante una clase de arranque.
Se ha agregado un hipervínculo nuevo con la etiqueta Select. Este vínculo envía el identificador del
instructor seleccionado al método Index y establece un color de fondo.
Ejecute la aplicación y haga clic en la pestaña Instructors. En la página se muestra la Location (oficina) de la
entidad OfficeAssignment relacionada. Si OfficeAssignment es NULL, se muestra una celda de tabla vacía.
Haga clic en el vínculo Select. El estilo de la fila cambia.
Agregar cursos impartidos por el instructor seleccionado
Actualice el método OnGetAsync de Pages/Instructors/Index.cshtml.cs con el código siguiente:
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = Instructor.Instructors.Where(
i => i.ID == id.Value).Single();
Instructor.Courses = instructor.CourseAssignments.Select(s => s.Course);
}
if (courseID != null)
{
CourseID = courseID.Value;
Instructor.Enrollments = Instructor.Courses.Where(
x => x.CourseID == courseID).Single().Enrollments;
}
}
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = Instructor.Instructors.Where(
i => i.ID == id.Value).Single();
Instructor.Courses = instructor.CourseAssignments.Select(s => s.Course);
}
if (courseID != null)
{
CourseID = courseID.Value;
Instructor.Enrollments = Instructor.Courses.Where(
x => x.CourseID == courseID).Single().Enrollments;
}
}
El método Where devuelve una colección. En el método Where anterior, solo se devuelve una entidad Instructor
. El método Single convierte la colección en una sola entidad Instructor . La entidad Instructor proporciona
acceso a la propiedad CourseAssignments . CourseAssignments proporciona acceso a las entidades Course
relacionadas.
El método Single se usa en una colección cuando la colección tiene un solo elemento. El método Single inicia
una excepción si la colección está vacía o hay más de un elemento. Una alternativa es SingleOrDefault , que
devuelve una valor predeterminado (NULL, en este caso) si la colección está vacía. El uso de SingleOrDefault en
una colección vacía:
Inicia una excepción (al tratar de buscar una propiedad Courses en una referencia nula).
El mensaje de excepción indicará con menos claridad la causa del problema.
El código siguiente rellena la propiedad Enrollments del modelo de vista cuando se selecciona un curso:
if (courseID != null)
{
CourseID = courseID.Value;
Instructor.Enrollments = Instructor.Courses.Where(
x => x.CourseID == courseID).Single().Enrollments;
}
</table>
}
En el marcado anterior se muestra una lista de cursos relacionados con un instructor cuando se selecciona un
instructor.
Pruebe la aplicación. Haga clic en un vínculo Select en la página de instructores.
Mostrar datos de estudiante
En esta sección, la aplicación se actualiza para mostrar los datos de estudiante para un curso seleccionado.
Actualice la consulta en el método OnGetAsync de Pages/Instructors/Index.cshtml.cs con el código siguiente:
En el marcado anterior se muestra una lista de los estudiantes que están inscritos en el curso seleccionado.
Actualice la página y seleccione un instructor. Seleccione un curso para ver la lista de los estudiantes inscritos y
sus calificaciones.
Uso de Single
Se puede pasar el método Single en la condición Where en lugar de llamar al método Where por separado:
public async Task OnGetAsync(int? id, int? courseID)
{
Instructor = new InstructorIndexData();
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = Instructor.Instructors.Single(
i => i.ID == id.Value);
Instructor.Courses = instructor.CourseAssignments.Select(
s => s.Course);
}
if (courseID != null)
{
CourseID = courseID.Value;
Instructor.Enrollments = Instructor.Courses.Single(
x => x.CourseID == courseID).Enrollments;
}
}
El enfoque de Single anterior no ofrece ninguna ventaja con respecto a Where . Algunos desarrolladores
prefieren el estilo del enfoque de Single .
Carga explícita
En el código actual se especifica la carga diligente para Enrollments y Students :
Imagine que los usuarios rara vez querrán ver las inscripciones en un curso. En ese caso, una optimización sería
cargar solamente los datos de inscripción si se solicitan. En esta sección, se actualiza OnGetAsync para usar la
carga explícita de Enrollments y Students .
Actualice OnGetAsync con el código siguiente:
public async Task OnGetAsync(int? id, int? courseID)
{
Instructor = new InstructorIndexData();
Instructor.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
//.Include(i => i.CourseAssignments)
// .ThenInclude(i => i.Course)
// .ThenInclude(i => i.Enrollments)
// .ThenInclude(i => i.Student)
// .AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = Instructor.Instructors.Where(
i => i.ID == id.Value).Single();
Instructor.Courses = instructor.CourseAssignments.Select(s => s.Course);
}
if (courseID != null)
{
CourseID = courseID.Value;
var selectedCourse = Instructor.Courses.Where(x => x.CourseID == courseID).Single();
await _context.Entry(selectedCourse).Collection(x => x.Enrollments).LoadAsync();
foreach (Enrollment enrollment in selectedCourse.Enrollments)
{
await _context.Entry(enrollment).Reference(x => x.Student).LoadAsync();
}
Instructor.Enrollments = selectedCourse.Enrollments;
}
}
En el código anterior se quitan las llamadas al método ThenInclude para los datos de inscripción y estudiantes. Si
se selecciona un curso, el código resaltado recupera lo siguiente:
Las entidades Enrollment para el curso seleccionado.
Las entidades Student para cada Enrollment .
Tenga en cuenta que en el código anterior .AsNoTracking() se convierte en comentario. Las propiedades de
navegación solo se pueden cargar explícitamente para las entidades sometidas a seguimiento.
Pruebe la aplicación. Desde la perspectiva de los usuarios, la aplicación se comporta exactamente igual a la
versión anterior.
En el siguiente tutorial se muestra cómo actualizar datos relacionados.
A N T E R IO R S IG U IE N T E
Páginas de Razor con EF Core en ASP.NET Core:
Actualización de datos relacionados (7 de 8)
25/06/2018 • 24 minutes to read • Edit Online
namespace ContosoUniversity.Pages.Courses
{
public class DepartmentNamePageModel : PageModel
{
public SelectList DepartmentNameSL { get; set; }
En el código anterior se crea una clase SelectList para que contenga la lista de nombres de departamento. Si se
especifica selectedDepartment , se selecciona ese departamento en la SelectList .
Las clases de modelo de página de Create y Edit se derivan de DepartmentNamePageModel .
namespace ContosoUniversity.Pages.Courses
{
public class CreateModel : DepartmentNamePageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
[BindProperty]
public Course Course { get; set; }
if (await TryUpdateModelAsync<Course>(
emptyCourse,
"course", // Prefix for form value.
s => s.CourseID, s => s.DepartmentID, s => s.Title, s => s.Credits))
{
_context.Courses.Add(emptyCourse);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
El código anterior:
Deriva de DepartmentNamePageModel .
Usa TryUpdateModelAsync para evitar la publicación excesiva.
Reemplaza ViewData["DepartmentID"] con DepartmentNameSL (de la clase base).
Pruebe la página Create. En la página Create se muestra el nombre del departamento en lugar del identificador.
Actualice la página Courses Edit.
Actualice el modelo de página de Edit con el código siguiente:
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;
namespace ContosoUniversity.Pages.Courses
{
public class EditModel : DepartmentNamePageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
[BindProperty]
public Course Course { get; set; }
if (Course == null)
{
return NotFound();
}
if (await TryUpdateModelAsync<Course>(
courseToUpdate,
"course", // Prefix for form value.
c => c.Credits, c => c.DepartmentID, c => c.Title))
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
Los cambios son similares a los realizados en el modelo de página de Create. En el código anterior,
PopulateDepartmentsDropDownList pasa el identificador de departamento, que selecciona el departamento
especificado en la lista desplegable.
Actualice Pages/Courses/Edit.cshtml con el marcado siguiente:
@page
@model ContosoUniversity.Pages.Courses.EditModel
@{
ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>Course</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Course.CourseID" />
<div class="form-group">
<label asp-for="Course.CourseID" class="control-label"></label>
<div>@Html.DisplayFor(model => model.Course.CourseID)</div>
</div>
<div class="form-group">
<label asp-for="Course.Title" class="control-label"></label>
<input asp-for="Course.Title" class="form-control" />
<span asp-validation-for="Course.Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Course.Credits" class="control-label"></label>
<input asp-for="Course.Credits" class="form-control" />
<span asp-validation-for="Course.Credits" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Course.Department" class="control-label"></label>
<select asp-for="Course.DepartmentID" class="form-control"
asp-items="@Model.DepartmentNameSL"></select>
<span asp-validation-for="Course.DepartmentID" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="./Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
La página contiene un campo oculto ( <input type="hidden"> ) para el número de curso. Agregar una aplicación
auxiliar de etiquetas <label> con asp-for="Course.CourseID" no elimina la necesidad del campo oculto. Se
requiere <input type="hidden"> para que el número de curso se incluya en los datos enviados cuando el usuario
hace clic en Guardar.
Pruebe el código actualizado. Cree, modifique y elimine un curso.
[BindProperty]
public Course Course { get; set; }
if (Course == null)
{
return NotFound();
}
return Page();
}
if (Course != null)
{
_context.Courses.Remove(Course);
await _context.SaveChangesAsync();
}
return RedirectToPage("./Index");
}
}
if (Course == null)
{
return NotFound();
}
return Page();
}
@{
ViewData["Title"] = "Delete";
}
<h2>Delete</h2>
<form method="post">
<input type="hidden" asp-for="Course.CourseID" />
<input type="submit" value="Delete" class="btn btn-default" /> |
<a asp-page="./Index">Back to List</a>
</form>
</div>
[BindProperty]
public Instructor Instructor { get; set; }
if (Instructor == null)
{
return NotFound();
}
return Page();
}
if (await TryUpdateModelAsync<Instructor>(
instructorToUpdate,
"Instructor",
i => i.FirstMidName, i => i.LastName,
i => i.HireDate, i => i.OfficeAssignment))
{
if (String.IsNullOrWhiteSpace(
instructorToUpdate.OfficeAssignment?.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
await _context.SaveChangesAsync();
}
return RedirectToPage("./Index");
}
}
El código anterior:
Obtiene la entidad Instructor actual de la base de datos mediante la carga diligente de la propiedad de
navegación OfficeAssignment .
Actualiza la entidad Instructor recuperada con valores del enlazador de modelos. TryUpdateModel evita la
publicación excesiva.
Si la ubicación de la oficina está en blanco, establece Instructor.OfficeAssignment en NULL. Cuando
Instructor.OfficeAssignment es NULL, se elimina la fila relacionada en la tabla OfficeAssignment .
@page
@model ContosoUniversity.Pages.Instructors.EditModel
@{
ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>Instructor</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Instructor.ID" />
<div class="form-group">
<label asp-for="Instructor.LastName" class="control-label"></label>
<input asp-for="Instructor.LastName" class="form-control" />
<span asp-validation-for="Instructor.LastName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Instructor.FirstMidName" class="control-label"></label>
<input asp-for="Instructor.FirstMidName" class="form-control" />
<span asp-validation-for="Instructor.FirstMidName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Instructor.HireDate" class="control-label"></label>
<input asp-for="Instructor.HireDate" class="form-control" />
<span asp-validation-for="Instructor.HireDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Instructor.OfficeAssignment.Location" class="control-label"></label>
<input asp-for="Instructor.OfficeAssignment.Location" class="form-control" />
<span asp-validation-for="Instructor.OfficeAssignment.Location" class="text-danger" />
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="./Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Course e Instructor tienen una relación de varios a varios. Para agregar y eliminar relaciones, agregue y quite
entidades del conjunto de entidades combinadas CourseAssignments .
Las casillas permiten cambios en los cursos a los que está asignado un instructor. Se muestra una casilla para
cada curso en la base de datos. Los cursos a los que el instructor está asignado se activan. El usuario puede activar
o desactivar las casillas para cambiar las asignaciones de cursos. Si el número de cursos fuera mucho mayor:
Probablemente usaría una interfaz de usuario diferente para mostrar los cursos.
El método de manipulación de una entidad de combinación para crear o eliminar relaciones no cambiaría.
Agregar clases para admitir las páginas de instructor Create y Edit
Cree SchoolViewModels/AssignedCourseData.cs con el código siguiente:
namespace ContosoUniversity.Models.SchoolViewModels
{
public class AssignedCourseData
{
public int CourseID { get; set; }
public string Title { get; set; }
public bool Assigned { get; set; }
}
}
La clase AssignedCourseData contiene datos para crear las casillas para los cursos asignados por un instructor.
Cree la clase base Pages/Instructors/InstructorCoursesPageModel.cshtml.cs:
using ContosoUniversity.Data;
using ContosoUniversity.Models;
using ContosoUniversity.Models.SchoolViewModels;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
using System.Linq;
namespace ContosoUniversity.Pages.Instructors
{
public class InstructorCoursesPageModel : PageModel
{
InstructorCoursesPageModel es la clase base que se usará para los modelos de página de Edit y Create.
PopulateAssignedCourseData lee todas las entidades Course para rellenar AssignedCourseDataList . Para cada
curso, el código establece el CourseID , el título y si el instructor está asignado o no al curso. Se usa un HashSet
para crear búsquedas eficaces.
Modelo de página de edición de instructores
Actualice el modelo de página de edición de instructores con el código siguiente:
public class EditModel : InstructorCoursesPageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
[BindProperty]
public Instructor Instructor { get; set; }
if (Instructor == null)
{
return NotFound();
}
PopulateAssignedCourseData(_context, Instructor);
return Page();
}
if (await TryUpdateModelAsync<Instructor>(
instructorToUpdate,
"Instructor",
i => i.FirstMidName, i => i.LastName,
i => i.HireDate, i => i.OfficeAssignment))
{
if (String.IsNullOrWhiteSpace(
instructorToUpdate.OfficeAssignment?.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
UpdateInstructorCourses(_context, selectedCourses, instructorToUpdate);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
UpdateInstructorCourses(_context, selectedCourses, instructorToUpdate);
PopulateAssignedCourseData(_context, instructorToUpdate);
return Page();
}
}
El código anterior controla los cambios de asignación de oficina.
Actualice la vista de Razor del instructor:
@page
@model ContosoUniversity.Pages.Instructors.EditModel
@{
ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>Instructor</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Instructor.ID" />
<div class="form-group">
<label asp-for="Instructor.LastName" class="control-label"></label>
<input asp-for="Instructor.LastName" class="form-control" />
<span asp-validation-for="Instructor.LastName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Instructor.FirstMidName" class="control-label"></label>
<input asp-for="Instructor.FirstMidName" class="form-control" />
<span asp-validation-for="Instructor.FirstMidName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Instructor.HireDate" class="control-label"></label>
<input asp-for="Instructor.HireDate" class="form-control" />
<span asp-validation-for="Instructor.HireDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Instructor.OfficeAssignment.Location" class="control-label"></label>
<input asp-for="Instructor.OfficeAssignment.Location" class="form-control" />
<span asp-validation-for="Instructor.OfficeAssignment.Location" class="text-danger" />
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<table>
<tr>
@{
int cnt = 0;
<div>
<a asp-page="./Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
NOTE
Al pegar el código en Visual Studio, se cambian los saltos de línea de tal forma que el código se interrumpe. Presione Ctrl+Z
una vez para deshacer el formato automático. Ctrl+Z corrige los saltos de línea para que se muestren como se ven aquí. No
es necesario que la sangría sea perfecta, pero las líneas @</tr><tr> , @:<td> , @:</td> y @:</tr> deben estar en una
única línea tal y como se muestra. Con el bloque de código nuevo seleccionado, presione tres veces la tecla Tab para
alinearlo con el código existente. Puede votar o revisar el estado de este error con este vínculo.
En el código anterior se crea una tabla HTML que tiene tres columnas. Cada columna tiene una casilla y una
leyenda que contiene el número y el título del curso. Todas las casillas tienen el mismo nombre
("selectedCourses"). Al usar el mismo nombre se informa al enlazador de modelos que las trate como un grupo. El
atributo de valor de cada casilla se establece en CourseID . Cuando se envía la página, el enlazador de modelos
pasa una matriz formada solo por los valores CourseID de las casillas activadas.
Cuando se representan las casillas por primera vez, los cursos asignados al instructor tienen atributos checked.
Ejecute la aplicación y pruebe la página Edit de los instructores actualizada. Cambie algunas asignaciones de
cursos. Los cambios se reflejan en la página Index.
Nota: El enfoque que se aplica aquí para modificar datos de los cursos del instructor funciona bien cuando hay un
número limitado de cursos. Para las colecciones que son mucho más grandes, una interfaz de usuario y un
método de actualización diferentes serían más eficaces y útiles.
Actualizar la página de creación de instructores
Actualice el modelo de página de creación de instructores con el código siguiente:
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ContosoUniversity.Pages.Instructors
{
public class CreateModel : InstructorCoursesPageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
[BindProperty]
public Instructor Instructor { get; set; }
if (await TryUpdateModelAsync<Instructor>(
newInstructor,
"Instructor",
i => i.FirstMidName, i => i.LastName,
i => i.HireDate, i => i.OfficeAssignment))
{
_context.Instructors.Add(newInstructor);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
PopulateAssignedCourseData(_context, newInstructor);
return Page();
}
}
}
@page
@model ContosoUniversity.Pages.Instructors.CreateModel
@{
ViewData["Title"] = "Create";
}
<h2>Create</h2>
<h4>Instructor</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Instructor.LastName" class="control-label"></label>
<input asp-for="Instructor.LastName" class="form-control" />
<span asp-validation-for="Instructor.LastName" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="Instructor.FirstMidName" class="control-label"></label>
<input asp-for="Instructor.FirstMidName" class="form-control" />
<span asp-validation-for="Instructor.FirstMidName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Instructor.HireDate" class="control-label"></label>
<input asp-for="Instructor.HireDate" class="form-control" />
<span asp-validation-for="Instructor.HireDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Instructor.OfficeAssignment.Location" class="control-label"></label>
<input asp-for="Instructor.OfficeAssignment.Location" class="form-control" />
<span asp-validation-for="Instructor.OfficeAssignment.Location" class="text-danger" />
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<table>
<tr>
@{
int cnt = 0;
<div>
<a asp-page="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
namespace ContosoUniversity.Pages.Instructors
{
public class DeleteModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
[BindProperty]
public Instructor Instructor { get; set; }
if (Instructor == null)
{
return NotFound();
}
return Page();
}
_context.Instructors.Remove(instructor);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
}
es-es/
Conflictos de simultaneidad
Un conflicto de simultaneidad se produce cuando:
Un usuario va a la página de edición de una entidad.
Otro usuario actualiza la misma entidad antes de que el cambio del primer usuario se escriba en la base de
datos.
Si no está habilitada la detección de simultaneidad, cuando se produzcan actualizaciones simultáneas:
Prevalece la última actualización. Es decir, los últimos valores de actualización se guardan en la base de datos.
La primera de las actualizaciones actuales se pierde.
Simultaneidad optimista
La simultaneidad optimista permite que se produzcan conflictos de simultaneidad y luego reacciona
correctamente si ocurren. Por ejemplo, Jane visita la página de edición de Department y cambia el presupuesto
para el departamento de inglés de 350.000,00 a 0,00 USD.
Antes de que Jane haga clic en Save, John visita la misma página y cambia el campo Start Date de 9/1/2007 a
9/1/2013.
Jane hace clic en Save primero y ve su cambio cuando el explorador muestra la página de índice.
John hace clic en Save en una página Edit que sigue mostrando un presupuesto de 350.000,00 USD. Lo que
sucede después viene determinado por cómo controla los conflictos de simultaneidad.
La simultaneidad optimista incluye las siguientes opciones:
Puede realizar un seguimiento de la propiedad que ha modificado un usuario y actualizar solo las
columnas correspondientes de la base de datos.
En el escenario, no se perderá ningún dato. Los dos usuarios actualizaron diferentes propiedades. La
próxima vez que un usuario examine el departamento de inglés, verá los cambios tanto de Jane como de
John. Este método de actualización puede reducir el número de conflictos que pueden dar lugar a una
pérdida de datos. Este enfoque: * No puede evitar la pérdida de datos si se realizan cambios paralelos a la
misma propiedad. * Por lo general no es práctico en una aplicación web. Requiere mantener un estado
significativo para realizar un seguimiento de todos los valores capturados y nuevos. El mantenimiento de
grandes cantidades de estado puede afectar al rendimiento de la aplicación. * Puede aumentar la
complejidad de las aplicaciones en comparación con la detección de simultaneidad en una entidad.
Puede permitir que los cambios de John sobrescriban los cambios de Jane.
La próxima vez que un usuario examine el departamento de inglés, verá 9/1/2013 y el valor de
350.000,00 USD capturado. Este enfoque se denomina un escenario de Prevalece el cliente o Prevalece el
último. (Todos los valores del cliente tienen prioridad sobre lo que aparece en el almacén de datos). Si no
hace ninguna codificación para el control de la simultaneidad, Prevalece el cliente se realizará
automáticamente.
Puede evitar que el cambio de John se actualice en la base de datos. Normalmente, la aplicación podría: *
Mostrar un mensaje de error. * Mostrar el estado actual de los datos. * Permitir al usuario volver a aplicar
los cambios.
Esto se denomina un escenario de Prevalece el almacén. (Los valores del almacén de datos tienen
prioridad sobre los valores enviados por el cliente). En este tutorial implementará el escenario de
Prevalece el almacén. Este método garantiza que ningún cambio se sobrescriba sin que se avise al
usuario.
Administrar la simultaneidad
Cuando una propiedad se configura como un token de simultaneidad:
EF Core comprueba que no se ha modificado la propiedad después de que se capturase. La comprobación se
produce cuando se llama a SaveChanges o SaveChangesAsync.
Si se ha cambiado la propiedad después de haberla capturado, se produce una excepción
DbUpdateConcurrencyException.
Deben configurarse el modelo de datos y la base de datos para que admitan producir una excepción
DbUpdateConcurrencyException .
Es específico de SQL Server. Otras bases de datos podrían no proporcionar una característica similar.
Se usa para determinar que no se ha cambiado una entidad desde que se capturó de la base de datos.
La base de datos genera un número rowversion secuencial que se incrementa cada vez que se actualiza la fila.
En un comando Update o Delete , la cláusula Where incluye el valor capturado de rowversion . Si la fila que se
está actualizando ha cambiado:
rowversionno coincide con el valor capturado.
Los comandos Update o Delete no encuentran una fila porque la cláusula Where incluye la rowversion
capturada.
Se produce una excepción DbUpdateConcurrencyException .
En EF Core, cuando un comando Update o Delete no han actualizado ninguna fila, se produce una excepción
de simultaneidad.
Agregar una propiedad de seguimiento a la entidad Department
En Models/Department.cs, agregue una propiedad de seguimiento denominada RowVersion:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Department
{
public int DepartmentID { get; set; }
[DataType(DataType.Currency)]
[Column(TypeName = "money")]
public decimal Budget { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Start Date")]
public DateTime StartDate { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
El atributo Timestamp especifica que esta columna se incluye en la cláusula Where de los comandos Update y
Delete . El atributo se denomina Timestamp porque las versiones anteriores de SQL Server usaban un tipo de
datos timestamp antes de que el tipo rowversion de SQL lo sustituyera por otro.
La API fluida también puede especificar la propiedad de seguimiento:
modelBuilder.Entity<Department>()
.Property<byte[]>("RowVersion")
.IsRowVersion();
El código siguiente muestra una parte del T-SQL generado por EF Core cuando se actualiza el nombre de
Department:
El código resaltado anteriormente muestra la cláusula WHERE que contiene RowVersion . Si la base de datos
RowVersion no es igual al parámetro RowVersion ( @p2 ), no se ha actualizado ninguna fila.
El código resaltado a continuación muestra el T-SQL que comprueba que se actualizó exactamente una fila:
@@ROWCOUNT devuelve el número de filas afectadas por la última instrucción. Si no se actualiza ninguna fila,
EF Core produce una excepción DbUpdateConcurrencyException .
Puede ver el T-SQL que genera EF Core en la ventana de salida de Visual Studio.
Actualizar la base de datos
Agregar la propiedad RowVersion cambia el modelo de base de datos, lo que requiere una migración.
Compile el proyecto. Escriba lo siguiente en una ventana de comandos:
modelBuilder.Entity("ContosoUniversity.Models.Department", b =>
{
b.Property<int>("DepartmentID")
.ValueGeneratedOnAdd();
b.Property<decimal>("Budget")
.HasColumnType("money");
b.Property<int?>("InstructorID");
b.Property<string>("Name")
.HasMaxLength(50);
b.Property<byte[]>("RowVersion")
.IsConcurrencyToken()
.ValueGeneratedOnAddOrUpdate();
b.Property<DateTime>("StartDate");
b.HasKey("DepartmentID");
b.HasIndex("InstructorID");
b.ToTable("Department");
});
El comando anterior aplica scaffolding al modelo Department . Abra el proyecto en Visual Studio.
Compile el proyecto. La compilación genera errores similares a los siguientes:
1>Pages/Departments/Index.cshtml.cs(26,37,26,43): error CS1061: 'SchoolContext' does not contain a definition
for 'Department' and no extension method 'Department' accepting a first argument of type 'SchoolContext'
could be found (are you missing a using directive or an assembly reference?)
Cambie globalmente _context.Department por _context.Departments (es decir, agregue una "s" a Department ).
Se encuentran y actualizan siete repeticiones.
Actualizar la página de índice de Departments
El motor de scaffolding creó una columna RowVersion para la página de índice, pero ese campo no debería
mostrarse. En este tutorial, el último byte de la RowVersion se muestra para ayudar a entender la simultaneidad.
No se garantiza que el último byte sea único. Una aplicación real no mostraría RowVersion ni el último byte de
RowVersion .
@{
ViewData["Title"] = "Departments";
}
<h2>Departments</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Department[0].Name)
</th>
<th>
@Html.DisplayNameFor(model => model.Department[0].Budget)
</th>
<th>
@Html.DisplayNameFor(model => model.Department[0].StartDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Department[0].Administrator)
</th>
<th>
RowVersion
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Department) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Budget)
</td>
<td>
@Html.DisplayFor(modelItem => item.StartDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Administrator.FullName)
</td>
<td>
@item.RowVersion[7]
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.DepartmentID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.DepartmentID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.DepartmentID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
using ContosoUniversity.Data;
using ContosoUniversity.Models;
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;
namespace ContosoUniversity.Pages.Departments
{
public class EditModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
[BindProperty]
public Department Department { get; set; }
// Replace ViewData["InstructorID"]
public SelectList InstructorNameSL { get; set; }
if (Department == null)
{
return NotFound();
}
return Page();
}
if (await TryUpdateModelAsync<Department>(
if (await TryUpdateModelAsync<Department>(
departmentToUpdate,
"Department",
s => s.Name, s => s.StartDate, s => s.Budget, s => s.InstructorID))
{
try
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch (DbUpdateConcurrencyException ex)
{
var exceptionEntry = ex.Entries.Single();
var clientValues = (Department)exceptionEntry.Entity;
var databaseEntry = exceptionEntry.GetDatabaseValues();
if (databaseEntry == null)
{
ModelState.AddModelError(string.Empty, "Unable to save. " +
"The department was deleted by another user.");
return Page();
}
return Page();
}
if (dbValues.Name != clientValues.Name)
{
ModelState.AddModelError("Department.Name",
$"Current value: {dbValues.Name}");
}
if (dbValues.Budget != clientValues.Budget)
{
ModelState.AddModelError("Department.Budget",
$"Current value: {dbValues.Budget:c}");
}
if (dbValues.StartDate != clientValues.StartDate)
{
ModelState.AddModelError("Department.StartDate",
$"Current value: {dbValues.StartDate:d}");
}
if (dbValues.InstructorID != clientValues.InstructorID)
{
Instructor dbInstructor = await _context.Instructors
.FindAsync(dbValues.InstructorID);
ModelState.AddModelError("Department.InstructorID",
$"Current value: {dbInstructor?.FullName}");
}
ModelState.AddModelError(string.Empty,
"The record you attempted to edit "
+ "was modified by another user after you. The "
+ "edit operation was canceled and the current values in the database "
+ "have been displayed. If you still want to edit this record, click "
+ "the Save button again.");
}
}
}
Para detectar un problema de simultaneidad, el OriginalValue se actualiza con el valor rowVersion de la entidad
de la que se capturó. EF Core genera un comando UPDATE de SQL con una cláusula WHERE que contiene el
valor RowVersion original. Si no hay ninguna fila afectada por el comando UPDATE (ninguna fila tiene el valor
RowVersion original), se produce una excepción DbUpdateConcurrencyException .
El código siguiente agrega un mensaje de error personalizado para cada columna que tiene valores de la base de
datos diferentes de lo que se registró en OnPostAsync :
if (dbValues.Name != clientValues.Name)
{
ModelState.AddModelError("Department.Name",
$"Current value: {dbValues.Name}");
}
if (dbValues.Budget != clientValues.Budget)
{
ModelState.AddModelError("Department.Budget",
$"Current value: {dbValues.Budget:c}");
}
if (dbValues.StartDate != clientValues.StartDate)
{
ModelState.AddModelError("Department.StartDate",
$"Current value: {dbValues.StartDate:d}");
}
if (dbValues.InstructorID != clientValues.InstructorID)
{
Instructor dbInstructor = await _context.Instructors
.FindAsync(dbValues.InstructorID);
ModelState.AddModelError("Department.InstructorID",
$"Current value: {dbInstructor?.FullName}");
}
ModelState.AddModelError(string.Empty,
"The record you attempted to edit "
+ "was modified by another user after you. The "
+ "edit operation was canceled and the current values in the database "
+ "have been displayed. If you still want to edit this record, click "
+ "the Save button again.");
}
El código resaltado a continuación establece el valor RowVersion para el nuevo valor recuperado de la base de
datos. La próxima vez que el usuario haga clic en Save, solo se detectarán los errores de simultaneidad que se
produzcan desde la última visualización de la página Edit.
try
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch (DbUpdateConcurrencyException ex)
{
var exceptionEntry = ex.Entries.Single();
var clientValues = (Department)exceptionEntry.Entity;
var databaseEntry = exceptionEntry.GetDatabaseValues();
if (databaseEntry == null)
{
ModelState.AddModelError(string.Empty, "Unable to save. " +
"The department was deleted by another user.");
return Page();
}
El marcado anterior:
Actualiza la directiva page de @page a @page "{id:int}" .
Agrega una versión de fila oculta. Se debe agregar RowVersion para que la devolución enlace el valor.
Muestra el último byte de RowVersion para fines de depuración.
Reemplaza ViewData con InstructorNameSL fuertemente tipadas.
El explorador muestra la página de índice con el valor modificado y el indicador rowVersion actualizado. Tenga
en cuenta el indicador rowVersion actualizado, que se muestra en el segundo postback en la otra pestaña.
Cambie otro campo en la segunda pestaña del explorador.
Haga clic en Guardar. Verá mensajes de error para todos los campos que no coinciden con los valores de la base
de datos:
Esta ventana del explorador no planeaba cambiar el campo Name. Copie y pegue el valor actual (Languages) en
el campo Name. Presione TAB para salir del campo. La validación del lado cliente quita el mensaje de error.
Vuelva a hacer clic en Save. Se guarda el valor especificado en la segunda pestaña del explorador. Verá los
valores guardados en la página de índice.
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;
namespace ContosoUniversity.Pages.Departments
{
public class DeleteModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
public DeleteModel(ContosoUniversity.Data.SchoolContext context)
{
_context = context;
}
[BindProperty]
public Department Department { get; set; }
public string ConcurrencyErrorMessage { get; set; }
if (Department == null)
{
return NotFound();
}
if (concurrencyError.GetValueOrDefault())
{
ConcurrencyErrorMessage = "The record you attempted to delete "
+ "was modified by another user after you selected delete. "
+ "The delete operation was canceled and the current values in the "
+ "database have been displayed. If you still want to delete this "
+ "record, click the Delete button again.";
}
return Page();
}
La página Delete detecta los conflictos de simultaneidad cuando la entidad ha cambiado después de que se
capturase. Department.RowVersion es la versión de fila cuando se capturó la entidad. Cuando EF Core crea el
comando DELETE de SQL, incluye una cláusula WHERE con RowVersion . Si el comando DELETE de SQL tiene
como resultado cero filas afectadas:
La RowVersion del comando DELETE de SQL no coincide con la RowVersion de la base de datos.
Se produce una excepción DbUpdateConcurrencyException.
Se llama a OnGetAsync con el concurrencyError .
Actualizar la página Delete
Actualice Pages/Departments/Delete.cshtml con el código siguiente:
@page "{id:int}"
@model ContosoUniversity.Pages.Departments.DeleteModel
@{
ViewData["Title"] = "Delete";
}
<h2>Delete</h2>
<p class="text-danger">@Model.ConcurrencyErrorMessage</p>
<form method="post">
<input type="hidden" asp-for="Department.DepartmentID" />
<input type="hidden" asp-for="Department.RowVersion" />
<div class="form-actions no-color">
<input type="submit" value="Delete" class="btn btn-default" /> |
<a asp-page="./Index">Back to List</a>
</div>
</form>
</div>
A N T E R IO R
ASP.NET Core MVC con EF Core: serie de tutoriales
21/06/2018 • 2 minutes to read • Edit Online
En este tutorial se explica el funcionamiento de ASP.NET Core MVC y Entity Framework Core con controladores y
vistas. Las páginas de Razor son una nueva alternativa en ASP.NET Core 2.0, un modelo de programación basado
en páginas que facilita la compilación de interfaces de usuario web y hace que sean más productivas.
Recomendamos el tutorial sobre las páginas de Razor antes que la versión MVC. El tutorial de las páginas de
Razor:
Es más fácil de seguir.
Proporciona más procedimientos recomendados de EF Core.
Usa consultas más eficaces.
Es más actual en relación con la API más reciente.
Abarca más características.
Es el método preferido para el desarrollo de nuevas aplicaciones.
Si elige este tutorial en lugar de la versión de páginas de Razor, le agradeceremos que nos explique el motivo en
este problema de GitHub.
1. Introducción
2. Operaciones de creación, lectura, actualización y eliminación
3. Ordenado, filtrado, paginación y agrupación
4. Migraciones
5. Creación de un modelo de datos complejo
6. Lectura de datos relacionados
7. Actualización de datos relacionados
8. Control de conflictos de simultaneidad
9. Herencia
10. Temas avanzados
ASP.NET Core MVC con Entity Framework Core:
Tutorial 1 de 10
25/06/2018 • 40 minutes to read • Edit Online
NOTE
Para la versión 1.1 de ASP.NET Core de este tutorial, vea la versión VS 2017 Update 2 de este tutorial en formato PDF.
Requisitos previos
Install one of the following:
CLI tooling: Windows, Linux, or macOS: .NET Core SDK 2.0 or later
IDE/editor tooling
Windows: Visual Studio for Windows
ASP.NET and web development workload
.NET Core cross-platform development workload
Linux: Visual Studio Code
macOS: Visual Studio for Mac
Solución de problemas
Si experimenta un problema que no puede resolver, por lo general podrá encontrar la solución si compara el
código con el proyecto completado. Para obtener una lista de errores comunes y cómo resolverlos, vea la
sección de solución de problemas del último tutorial de la serie. Si ahí no encuentra lo que necesita, puede
publicar una pregunta en StackOverflow.com para ASP.NET Core o EF Core.
TIP
Esta es una serie de 10 tutoriales y cada uno se basa en lo que se realiza en los anteriores. Considere la posibilidad de
guardar una copia del proyecto después de completar correctamente cada tutorial. Después, si experimenta problemas,
puede empezar desde el tutorial anterior en lugar de volver al principio de la serie completa.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Contoso University</title>
<environment names="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
</environment>
<environment names="Staging,Production">
<link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-
value="absolute" />
value="absolute" />
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
</environment>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-
collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a asp-area="" asp-controller="Home" asp-action="Index" class="navbar-brand">Contoso
University</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li>
<li><a asp-area="" asp-controller="Home" asp-action="About">About</a></li>
<li><a asp-area="" asp-controller="Students" asp-action="Index">Students</a></li>
<li><a asp-area="" asp-controller="Courses" asp-action="Index">Courses</a></li>
<li><a asp-area="" asp-controller="Instructors" asp-action="Index">Instructors</a></li>
<li><a asp-area="" asp-controller="Departments" asp-action="Index">Departments</a></li>
</ul>
</div>
</div>
</nav>
<div class="container body-content">
@RenderBody()
<hr />
<footer>
<p>© 2017 - Contoso University</p>
</footer>
</div>
<environment names="Development">
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
</environment>
<environment names="Staging,Production">
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js"
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery"
crossorigin="anonymous"
integrity="sha384-K+ctZQ+LL8q6tP7I94W+qzQsfRV2a+AfHIi9k8z8l9ggpc8X+Ytst4yBo/hH+8Fk">
</script>
<script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
crossorigin="anonymous"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa">
</script>
<script src="~/js/site.min.js" asp-append-version="true"></script>
</environment>
En Views/Home/Index.cshtml, reemplace el contenido del archivo con el código siguiente para reemplazar el
texto sobre ASP.NET y MVC con texto sobre esta aplicación:
@{
ViewData["Title"] = "Home Page";
}
<div class="jumbotron">
<h1>Contoso University</h1>
</div>
<div class="row">
<div class="col-md-4">
<h2>Welcome to Contoso University</h2>
<p>
Contoso University is a sample application that
demonstrates how to use Entity Framework Core in an
ASP.NET Core MVC web application.
</p>
</div>
<div class="col-md-4">
<h2>Build it from scratch</h2>
<p>You can build the application by following the steps in a series of tutorials.</p>
<p><a class="btn btn-default" href="https://docs.asp.net/en/latest/data/ef-mvc/intro.html">See the
tutorial »</a></p>
</div>
<div class="col-md-4">
<h2>Download it</h2>
<p>You can download the completed project from GitHub.</p>
<p><a class="btn btn-default" href="https://github.com/aspnet/Docs/tree/master/aspnetcore/data/ef-
mvc/intro/samples/cu-final">See project source code »</a></p>
</div>
</div>
Presione CTRL+F5 para ejecutar el proyecto o seleccione Depurar > Iniciar sin depurar en el menú. Verá la
página principal con pestañas para las páginas que se crearán en estos tutoriales.
Paquetes NuGet de Entity Framework Core
Para agregar compatibilidad con EF Core a un proyecto, instale el proveedor de base de datos que quiera tener
como destino. En este tutorial se usa SQL Server y el paquete de proveedor es
Microsoft.EntityFrameworkCore.SqlServer. Este paquete se incluye en el metapaquete
Microsoft.AspNetCore.All, por lo que no es necesario instalarlo.
Este paquete y sus dependencias ( Microsoft.EntityFrameworkCore y Microsoft.EntityFrameworkCore.Relational )
proporcionan compatibilidad en tiempo de ejecución para EF. Más adelante, en el tutorial Migraciones,
agregará un paquete de herramientas.
Para obtener información sobre otros proveedores de base de datos disponibles para Entity Framework Core,
vea Proveedores de bases de datos.
En la carpeta Models, cree un archivo de clase denominado Student.cs y reemplace el código de plantilla con el
código siguiente.
using System;
using System.Collections.Generic;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
La propiedad ID se convertirá en la columna de clave principal de la tabla de base de datos que corresponde
a esta clase. De forma predeterminada, Entity Framework interpreta como la clave principal una propiedad que
se denomine ID o classnameID .
La propiedad Enrollments es una propiedad de navegación. Las propiedades de navegación contienen otras
entidades relacionadas con esta entidad. En este caso, la propiedad Enrollments de una Student entity
contendrá todas las entidades Enrollment que estén relacionadas con esa entidad Student . En otras palabras,
si una fila Student determinada en la base de datos tiene dos filas Enrollment relacionadas (filas que contienen
el valor de clave principal de ese estudiante en la columna de clave externa StudentID ), la propiedad de
navegación Enrollments de esa entidad Student contendrá esas dos entidades Enrollment .
Si una propiedad de navegación puede contener varias entidades (como en las relaciones de varios a varios o
uno a varios), su tipo debe ser una lista a la que se puedan agregar las entradas, eliminarlas y actualizarlas,
como ICollection<T> . Puede especificar ICollection<T> o un tipo como List<T> o HashSet<T> . Si especifica
ICollection<T> , EF crea una colección HashSet<T> de forma predeterminada.
La entidad Enrollment
En la carpeta Models, cree Enrollment.cs y reemplace el código existente con el código siguiente:
namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}
La propiedad EnrollmentID será la clave principal; esta entidad usa el patrón classnameID en lugar de ID por
sí solo, como se vio en la entidad Student . Normalmente debería elegir un patrón y usarlo en todo el modelo
de datos. En este caso, la variación muestra que se puede usar cualquiera de los patrones. En un tutorial
posterior, verá cómo el uso de ID sin un nombre de clase facilita la implementación de la herencia en el
modelo de datos.
La propiedad Grade es una enum . El signo de interrogación después de la declaración de tipo Grade indica
que la propiedad Grade acepta valores NULL. Una calificación que sea NULL es diferente de una calificación
que sea cero; NULL significa que no se conoce una calificación o que todavía no se ha asignado.
La propiedad StudentID es una clave externa y la propiedad de navegación correspondiente es Student . Una
entidad Enrollment está asociada con una entidad Student , por lo que la propiedad solo puede contener un
única entidad Student (a diferencia de la propiedad de navegación Student.Enrollments que se vio
anteriormente, que puede contener varias entidades Enrollment ).
La propiedad CourseID es una clave externa y la propiedad de navegación correspondiente es Course . Una
entidad Enrollment está asociada con una entidad Course .
Entity Framework interpreta una propiedad como propiedad de clave externa si se denomina
<navigation property name><primary key property name> (por ejemplo StudentID para la propiedad de
navegación Student , dado que la clave principal de la entidad Student es ID ). Las propiedades de clave
externa también se pueden denominar simplemente <primary key property name> (por ejemplo CourseID ,
dado que la clave principal de la entidad Course es CourseID ).
La entidad Course
En la carpeta Models, cree Course.cs y reemplace el código existente con el código siguiente:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
La propiedad Enrollments es una propiedad de navegación. Una entidad Course puede estar relacionada con
cualquier número de entidades Enrollment .
En un tutorial posterior de esta serie se incluirá más información sobre el atributo DatabaseGenerated .
Básicamente, este atributo permite escribir la clave principal para el curso en lugar de hacer que la base de
datos lo genere.
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
Este código crea una propiedad DbSet para cada conjunto de entidades. En la terminología de Entity
Framework, un conjunto de entidades suele corresponderse con una tabla de base de datos, mientras que una
entidad lo hace con una fila de la tabla.
Se podrían haber omitido las instrucciones DbSet<Enrollment> y DbSet<Course> , y el funcionamiento sería el
mismo. Entity Framework las incluiría implícitamente porque la entidad Student hace referencia a la entidad
Enrollment y la entidad Enrollment hace referencia a la entidad Course .
Cuando se crea la base de datos, EF crea las tablas con los mismos nombres que los nombres de propiedad
DbSet . Los nombres de propiedad para las colecciones normalmente están en plural ( Students en lugar de
Student), pero los desarrolladores no están de acuerdo sobre si los nombres de tabla deben estar en plural o
no. Para estos tutoriales, se invalidará el comportamiento predeterminado mediante la especificación de
nombres de tabla en singular en DbContext. Para ello, agregue el código resaltado siguiente después de la
última propiedad DbSet.
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
services.AddMvc();
}
El nombre de la cadena de conexión se pasa al contexto mediante una llamada a un método en un objeto
DbContextOptionsBuilder . Para el desarrollo local, el sistema de configuración de ASP.NET Core lee la cadena
de conexión desde el archivo appsettings.json.
Agregue instrucciones para los espacios de nombres ContosoUniversity.Data y
using
Microsoft.EntityFrameworkCore , y después compile el proyecto.
using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
Abra el archivo appsettings.json y agregue una cadena de conexión como se muestra en el ejemplo siguiente.
{
"ConnectionStrings": {
"DefaultConnection": "Server=
(localdb)\\mssqllocaldb;Database=ContosoUniversity1;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
}
}
using ContosoUniversity.Models;
using System;
using System.Linq;
namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
context.Database.EnsureCreated();
El código comprueba si hay estudiantes en la base de datos, y si no es así, asume que la base de datos es nueva
y debe inicializarse con datos de prueba. Carga los datos de prueba en matrices en lugar de colecciones
List<T> para optimizar el rendimiento.
En Program.cs, modifique el método Main para que haga lo siguiente al iniciar la aplicación:
Obtener una instancia del contexto de base de datos desde el contenedor de inserción de dependencias.
Llamar al método de inicialización, pasándolo al contexto.
Eliminar el contexto cuando el método de inicialización haya finalizado.
host.Run();
}
using Microsoft.Extensions.DependencyInjection;
using ContosoUniversity.Data;
En los tutoriales anteriores, es posible que vea código similar en el método Configure de Startup.cs. Se
recomienda usar el método Configure solo para configurar la canalización de solicitudes. El código de inicio
de la aplicación pertenece al método Main .
Ahora, la primera vez que ejecute la aplicación, se creará la base de datos y se inicializará con datos de prueba.
Cada vez que cambie el modelo de datos, puede eliminar la base de datos, actualizar el método de
inicialización y comenzar desde cero con una base de datos nueva del mismo modo. En los tutoriales
posteriores, verá cómo modificar la base de datos cuando cambie el modelo de datos, sin tener que eliminarla
y volver a crearla.
Al hacer clic en Agregar, el motor de scaffolding de Visual Studio crea un archivo StudentsController.cs
y un conjunto de vistas (archivos .cshtml) que funcionan con el controlador.
(El motor de scaffolding también puede crear el contexto de base de datos de forma automática si no lo crea
primero manualmente como se hizo antes en este tutorial. Puede especificar una clase de contexto nueva en el
cuadro Agregar controlador si hace clic en el signo más situado a la derecha de Clase del contexto de
datos. Después, Visual Studio creará la clase DbContext , así como el controlador y las vistas).
Observará que el controlador toma un SchoolContext como parámetro de constructor.
namespace ContosoUniversity.Controllers
{
public class StudentsController : Controller
{
private readonly SchoolContext _context;
Más adelante en el tutorial obtendrá información sobre los elementos de programación asincrónicos de este
código.
En la vista Views/Students/Index.cshtml se muestra esta lista en una tabla:
@model IEnumerable<ContosoUniversity.Models.Student>
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.LastName)
</th>
<th>
@Html.DisplayNameFor(model => model.FirstMidName)
</th>
<th>
@Html.DisplayNameFor(model => model.EnrollmentDate)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.ID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Presione CTRL+F5 para ejecutar el proyecto o seleccione Depurar > Iniciar sin depurar en el menú.
Haga clic en la pestaña Students para ver los datos de prueba insertados por el método
DbInitializer.Initialize . En función del ancho de la ventana del explorador, verá el vínculo de la pestaña
Student en la parte superior de la página o tendrá que hacer clic en el icono de navegación en la esquina
superior derecha para verlo.
Ver la base de datos
Al iniciar la aplicación, el método DbInitializer.Initialize llama a EnsureCreated . EF comprobó que no había
ninguna base de datos y creó una, y después el resto del código del método Initialize la rellenó con datos.
Puede usar el Explorador de objetos de SQL Server (SSOX) para ver la base de datos en Visual Studio.
Cierre el explorador.
Si la ventana de SSOX no está abierta, selecciónela en el menú Vista de Visual Studio.
En SSOX, haga clic en (localdb)\MSSQLLocalDB > Databases y después en la entrada del nombre de base
de datos que se encuentra en la cadena de conexión del archivo appsettings.json.
Expanda el nodo Tablas para ver las tablas de la base de datos.
Haga clic con el botón derecho en la tabla Student y haga clic en Ver datos para ver las columnas que se
crearon y las filas que se insertaron en la tabla.
Convenciones
La cantidad de código que tendría que escribir para que Entity Framework pudiera crear una base de datos
completa para usted es mínima debido al uso de convenciones o las suposiciones que hace Entity Framework.
Los nombres de las propiedades DbSet se usan como nombres de tabla. Para las entidades a las que no
se hace referencia con una propiedad DbSet , los nombres de clase de entidad se usan como nombres
de tabla.
Los nombres de propiedad de entidad se usan para los nombres de columna.
Las propiedades de entidad que se denominan ID o classnameID se reconocen como propiedades de
clave principal.
Una propiedad se interpreta como propiedad de clave externa si se denomina (por ejemplo, StudentID
para la propiedad de navegación Student , dado que la clave principal de la entidad Student es ID ).
Las propiedades de clave externa también se pueden denominar simplemente (por ejemplo
EnrollmentID , dado que la clave principal de la entidad Enrollment es EnrollmentID ).
El comportamiento de las convenciones se puede reemplazar. Por ejemplo, puede especificar explícitamente los
nombres de tabla, como se vio anteriormente en este tutorial. Y puede establecer los nombres de columna y
cualquier propiedad como clave principal o clave externa, como verá en un tutorial posterior de esta serie.
Código asincrónico
La programación asincrónica es el modo predeterminado de ASP.NET Core y EF Core.
Un servidor web tiene un número limitado de subprocesos disponibles y, en situaciones de carga alta, es
posible que todos los subprocesos disponibles estén en uso. Cuando esto ocurre, el servidor no puede
procesar nuevas solicitudes hasta que los subprocesos se liberen. Con el código sincrónico, se pueden
acumular muchos subprocesos mientras no estén realizando ningún trabajo porque están a la espera de que
finalice la E/S. Con el código asincrónico, cuando un proceso está a la espera de que finalice la E/S, se libera su
subproceso para el que el servidor lo use para el procesamiento de otras solicitudes. Como resultado, el código
asincrónico permite que los recursos de servidor se usen de forma más eficaz, y el servidor está habilitado
para administrar más tráfico sin retrasos.
El código asincrónico introduce una pequeña cantidad de sobrecarga en tiempo de ejecución, pero para
situaciones de poco tráfico la disminución del rendimiento es insignificante, mientras que en situaciones de
tráfico elevado, la posible mejora del rendimiento es importante.
En el código siguiente, la palabra clave async , el valor devuelto Task<T> , la palabra clave await y el método
ToListAsync hacen que el código se ejecute de forma asincrónica.
La palabra clave async indica al compilador que genere devoluciones de llamada para partes del
cuerpo del método y que cree automáticamente el objeto Task<IActionResult> que se devuelve.
El tipo de valor devuelto Task<IActionResult> representa el trabajo en curso con un resultado de tipo
IActionResult .
La palabra clave await hace que el compilador divida el método en dos partes. La primera parte
termina con la operación que se inició de forma asincrónica. La segunda parte se coloca en un método
de devolución de llamada que se llama cuando finaliza la operación.
ToListAsync es la versión asincrónica del método de extensión ToList .
Algunos aspectos que tener en cuenta al escribir código asincrónico en el que se usa Entity Framework son los
siguientes:
Solo se ejecutan de forma asincrónica las instrucciones que hacen que las consultas o los comandos se
envíen a la base de datos. Eso incluye, por ejemplo, ToListAsync , SingleOrDefaultAsync y
SaveChangesAsync . No incluye, por ejemplo, instrucciones que solo cambian una IQueryable , como
var students = context.Students.Where(s => s.LastName == "Davolio") .
S IG U IE N T E
ASP.NET Core MVC con EF Core: CRUD (2 de 10)
25/06/2018 • 38 minutes to read • Edit Online
NOTE
Es una práctica habitual implementar el modelo de repositorio con el fin de crear una capa de abstracción entre el
controlador y la capa de acceso a datos. Para que estos tutoriales sean sencillos y se centren en enseñar a usar Entity
Framework, no se usan repositorios. Para obtener información sobre los repositorios con EF, vea el último tutorial de esta
serie.
if (student == null)
{
return NotFound();
}
return View(student);
}
Los métodos Include y ThenInclude hacen que el contexto cargue la propiedad de navegación
Student.Enrollments y, dentro de cada inscripción, la propiedad de navegación Enrollment.Course . Obtendrá más
información sobre estos métodos en el tutorial de lectura de datos relacionados.
El método AsNoTracking mejora el rendimiento en casos en los que no se actualizarán las entidades devueltas en
la duración del contexto actual. Obtendrá más información sobre AsNoTracking al final de este tutorial.
Datos de ruta
El valor de clave que se pasa al método Details procede de los datos de ruta. Los datos de ruta son los que el
enlazador de modelos encuentra en un segmento de la dirección URL. Por ejemplo, la ruta predeterminada
especifica los segmentos de controlador, acción e identificador:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
En la dirección URL siguiente, la ruta predeterminada asigna Instructor como el controlador, Index como la
acción y 1 como el identificador; estos son los valores de datos de ruta.
http://localhost:1230/Instructor/Index/1?courseID=2021
http://localhost:1230/Instructor/Index?id=1&CourseID=2021
En la página Index, las instrucciones de la aplicación auxiliar de etiquetas crean direcciones URL de hipervínculo
en la vista de Razor. En el siguiente código de Razor, el parámetro id coincide con la ruta predeterminada, por lo
que se agrega id a los datos de ruta.
<a href="/Students/Edit/6">Edit</a>
En el siguiente código de Razor, studentID no coincide con un parámetro en la ruta predeterminada, por lo que
se agrega como una cadena de consulta.
<a href="/Students/Edit?studentID=6">Edit</a>
Para obtener más información sobre las aplicaciones auxiliares de etiquetas, vea Aplicaciones auxiliares de
etiquetas en ASP.NET Core.
Agregar inscripciones a la vista de detalles
Abra Views/Students/Details.cshtml. Cada campo se muestra mediante las aplicaciones auxiliares DisplayNameFor
y DisplayFor , como se muestra en el ejemplo siguiente:
<dt>
@Html.DisplayNameFor(model => model.LastName)
</dt>
<dd>
@Html.DisplayFor(model => model.LastName)
</dd>
Después del último campo e inmediatamente antes de la etiqueta </dl> de cierre, agregue el código siguiente
para mostrar una lista de las inscripciones:
<dt>
@Html.DisplayNameFor(model => model.Enrollments)
</dt>
<dd>
<table class="table">
<tr>
<th>Course Title</th>
<th>Grade</th>
</tr>
@foreach (var item in Model.Enrollments)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Course.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
</tr>
}
</table>
</dd>
Si la sangría de código no es correcta después de pegar el código, presione CTRL -K-D para corregirlo.
Este código recorre en bucle las entidades en la propiedad de navegación Enrollments . Para cada inscripción, se
muestra el título del curso y la calificación. El título del curso se recupera de la entidad Course almacenada en la
propiedad de navegación Course de la entidad Enrollments.
Ejecute la aplicación, haga clic en la pestaña Students y después en el vínculo Details de un estudiante. Verá la
lista de cursos y calificaciones para el alumno seleccionado:
Actualizar la página Create
En StudentsController.cs, modifique el método HttpPost Create agregando un bloque try-catch y quitando ID
del atributo Bind .
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
[Bind("EnrollmentDate,FirstMidName,LastName")] Student student)
{
try
{
if (ModelState.IsValid)
{
_context.Add(student);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists " +
"see your system administrator.");
}
return View(student);
}
Este código agrega la entidad Student creada por el enlazador de modelos de ASP.NET MVC al conjunto de
entidades Students y después guarda los cambios en la base de datos. (El enlazador de modelos se refiere a la
funcionalidad de ASP.NET MVC que facilita trabajar con datos enviados por un formulario; un enlazador de
modelos convierte los valores de formulario enviados en tipos CLR y los pasa al método de acción en
parámetros. En este caso, el enlazador de modelos crea instancias de una entidad Student mediante valores de
propiedad de la colección Form).
Se ha quitado ID del atributo Bind porque ID es el valor de clave principal que SQL Server establecerá
automáticamente cuando se inserte la fila. La entrada del usuario no establece el valor ID.
Aparte del atributo Bind , el bloque try-catch es el único cambio que se ha realizado en el código con scaffolding.
Si se detecta una excepción derivada de DbUpdateException mientras se guardan los cambios, se muestra un
mensaje de error genérico. En ocasiones, las excepciones DbUpdateException se deben a algo externo a la
aplicación y no a un error de programación, por lo que se recomienda al usuario que vuelva a intentarlo. Aunque
no se ha implementado en este ejemplo, en una aplicación de producción de calidad se debería registrar la
excepción. Para obtener más información, vea la sección Registro para obtener información de Supervisión y
telemetría (creación de aplicaciones de nube reales con Azure).
El atributo ValidateAntiForgeryToken ayuda a evitar ataques de falsificación de solicitud entre sitios (CSRF ). El
token se inserta automáticamente en la vista por medio de FormTagHelper y se incluye cuando el usuario envía
el formulario. El token se valida mediante el atributo ValidateAntiForgeryToken . Para obtener más información
sobre CSRF, vea Prevención de ataques de falsificación de solicitud.
Nota de seguridad sobre la publicación excesiva
El atributo Bind que el código con scaffolding incluye en el método Create es una manera de protegerse contra
la publicación excesiva en escenarios de creación. Por ejemplo, suponga que la entidad Student incluye una
propiedad Secret que no quiere que esta página web establezca.
Aunque no tenga un campo Secret en la página web, un hacker podría usar una herramienta como Fiddler, o
bien escribir código de JavaScript, para enviar un valor de formulario Secret . Sin el atributo Bind para limitar
los campos que el enlazador de modelos usa cuando crea una instancia Student, el enlazador de modelos
seleccionaría ese valor de formulario Secret y lo usaría para crear la instancia de la entidad Student. Después, el
valor que el hacker haya especificado para el campo de formulario Secret se actualizaría en la base de datos. En
la imagen siguiente se muestra cómo la herramienta Fiddler agrega el campo Secret (con el valor "OverPost") a
los valores de formulario enviados.
Después, el valor "OverPost" se agregaría correctamente a la propiedad Secret de la fila insertada, aunque no
hubiera previsto que la página web pudiera establecer esa propiedad.
Puede evitar la publicación excesiva en escenarios de edición si primero lee la entidad desde la base de datos y
después llama a TryUpdateModel , pasando una lista de propiedades permitidas de manera explícita. Es el método
que se usa en estos tutoriales.
Una manera alternativa de evitar la publicación excesiva que muchos desarrolladores prefieren consiste en usar
modelos de vista en lugar de clases de entidad con el enlace de modelos. Incluya en el modelo de vista solo las
propiedades que quiera actualizar. Una vez que haya finalizado el enlazador de modelos de MVC, copie las
propiedades del modelo de vista a la instancia de entidad, opcionalmente con una herramienta como
AutoMapper. Use _context.Entry en la instancia de entidad para establecer su estado en Unchanged y, después,
establezca Property("PropertyName").IsModified en true en todas las propiedades de entidad que se incluyan en
el modelo de vista. Este método funciona tanto en escenarios de edición como de creación.
Probar la página Create
En el código de Views/Students/Create.cshtml se usan las aplicaciones auxiliares de etiquetas label , input y
span (para los mensajes de validación) en cada campo.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
[Bind("EnrollmentDate,FirstMidName,LastName")] Student student)
{
try
{
if (ModelState.IsValid)
{
_context.Add(student);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists " +
"see your system administrator.");
}
return View(student);
}
Cambie la fecha por un valor válido y haga clic en Crear para ver el alumno nuevo en la página Index.
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditPost(int? id)
{
if (id == null)
{
return NotFound();
}
var studentToUpdate = await _context.Students.SingleOrDefaultAsync(s => s.ID == id);
if (await TryUpdateModelAsync<Student>(
studentToUpdate,
"",
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{
try
{
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
}
return View(studentToUpdate);
}
Estos cambios implementan un procedimiento recomendado de seguridad para evitar la publicación excesiva. El
proveedor de scaffolding generó un atributo Bind y agregó la entidad creada por el enlazador de modelos a la
entidad establecida con una marca Modified . Ese código no se recomienda para muchos escenarios porque el
atributo Bind borra los datos ya existentes en los campos que no se enumeran en el parámetro Include .
El código nuevo lee la entidad existente y llama a TryUpdateModel para actualizar los campos en la entidad
recuperada en función de la entrada del usuario en los datos de formulario publicados. El seguimiento de
cambios automático de Entity Framework establece la marca Modified en los campos que se cambian mediante
la entrada de formulario. Cuando se llama al método SaveChanges , Entity Framework crea instrucciones SQL
para actualizar la fila de la base de datos. Los conflictos de simultaneidad se ignoran y las columnas de tabla que
se actualizaron por el usuario se actualizan en la base de datos. (En un tutorial posterior se muestra cómo
controlar los conflictos de simultaneidad).
Como procedimiento recomendado para evitar la publicación excesiva, los campos que quiera que se puedan
actualizar por la página Edit se incluyen en la lista de permitidos en los parámetros TryUpdateModel . (La cadena
vacía que precede a la lista de campos en la lista de parámetros es para el prefijo que se usa con los nombres de
campos de formulario). Actualmente no se está protegiendo ningún campo adicional, pero enumerar los campos
que quiere que el enlazador de modelos enlace garantiza que, si en el futuro agrega campos al modelo de datos,
se protejan automáticamente hasta que los agregue aquí de forma explícita.
Como resultado de estos cambios, la firma de método del método HttpPost Edit es la misma que la del método
HttpGet Edit ; por tanto, se ha cambiado el nombre del método EditPost .
Código alternativo para HttpPost Edit: crear y adjuntar
El código recomendado para HttpPost Edit garantiza que solo se actualicen las columnas cambiadas y conserva
los datos de las propiedades que no quiere que se incluyan para el enlace de modelos. Pero el enfoque de
primera lectura requiere una operación de lectura adicional de la base de datos y puede dar lugar a código más
complejo para controlar los conflictos de simultaneidad. Una alternativa consiste en adjuntar una entidad creada
por el enlazador de modelos en el contexto de EF y marcarla como modificada. (No actualice el proyecto con este
código, solo se muestra para ilustrar un enfoque opcional).
Puede usar este enfoque cuando la interfaz de usuario de la página web incluya todos los campos de la entidad y
puede actualizar cualquiera de ellos.
En el código con scaffolding se usa el enfoque de crear y adjuntar, pero solo se detectan las excepciones
DbUpdateConcurrencyException y se devuelven códigos de error 404. En el ejemplo mostrado se detecta cualquier
excepción de actualización de base de datos y se muestra un mensaje de error.
Estados de entidad
El contexto de la base de datos realiza el seguimiento de si las entidades en memoria están sincronizadas con sus
filas correspondientes en la base de datos, y esta información determina lo que ocurre cuando se llama al método
SaveChanges . Por ejemplo, cuando se pasa una nueva entidad al método Add , el estado de esa entidad se
establece en Added . Después, cuando se llama al método SaveChanges , el contexto de la base de datos emite un
comando INSERT de SQL.
Una entidad puede estar en uno de los estados siguientes:
. La entidad no existe todavía en la base de datos. El método
Added SaveChanges emite una instrucción
INSERT.
Unchanged. No es necesario hacer nada con esta entidad mediante el método SaveChanges . Al leer una
entidad de la base de datos, la entidad empieza con este estado.
Modified . Se han modificado algunos o todos los valores de propiedad de la entidad. El método
SaveChanges emite una instrucción UPDATE.
Deleted . La entidad se ha marcado para su eliminación. El método SaveChanges emite una instrucción
DELETE.
Detached . El contexto de base de datos no está realizando el seguimiento de la entidad.
En una aplicación de escritorio, los cambios de estado normalmente se establecen de forma automática. Lea una
entidad y realice cambios en algunos de sus valores de propiedad. Esto hace que su estado de entidad cambie
automáticamente a Modified . Después, cuando se llama a SaveChanges , Entity Framework genera una
instrucción UPDATE de SQL que solo actualiza las propiedades reales que se hayan cambiado.
En una aplicación web, el DbContext que inicialmente lee una entidad y muestra sus datos para que se puedan
modificar se elimina después de representar una página. Cuando se llama al método de acción HttpPost Edit , se
realiza una nueva solicitud web y se obtiene una nueva instancia de DbContext . Si vuelve a leer la entidad en ese
contexto nuevo, simulará el procesamiento de escritorio.
Pero si no quiere realizar la operación de lectura adicional, tendrá que usar el objeto de entidad creado por el
enlazador de modelos. La manera más sencilla de hacerlo consiste en establecer el estado de la entidad en
Modified tal y como se hace en el código HttpPost Edit alternativo mostrado anteriormente. Después, cuando se
llama a SaveChanges , Entity Framework actualiza todas las columnas de la fila de la base de datos, porque el
contexto no tiene ninguna manera de saber qué propiedades se han cambiado.
Si quiere evitar el enfoque de primera lectura pero también que la instrucción UPDATE de SQL actualice solo los
campos que el usuario ha cambiado realmente, el código es más complejo. Debe guardar los valores originales
de alguna manera (por ejemplo mediante campos ocultos) para que estén disponibles cuando se llame al método
HttpPost Edit . Después puede crear una entidad Student con los valores originales, llamar al método Attach
con esa versión original de la entidad, actualizar los valores de la entidad con los valores nuevos y luego llamar a
SaveChanges .
Cambie algunos de los datos y haga clic en Guardar. Se abrirá la página Index y verá los datos modificados.
Como se vio para las operaciones de actualización y creación, las operaciones de eliminación requieren dos
métodos de acción. El método que se llama en respuesta a una solicitud GET muestra una vista que proporciona
al usuario la oportunidad de aprobar o cancelar la operación de eliminación. Si el usuario la aprueba, se crea una
solicitud POST. Cuando esto ocurre, se llama al método HttpPost Delete y, después, ese método es el que realiza
la operación de eliminación.
Agregará un bloque try-catch al método HttpPost Delete para controlar los errores que puedan producirse
cuando se actualice la base de datos. Si se produce un error, el método HttpPost Delete llama al método HttpGet
Delete, pasando un parámetro que indica que se ha producido un error. Después, el método HttpGet Delete
vuelve a mostrar la página de confirmación junto con el mensaje de error, dando al usuario la oportunidad de
cancelar o volver a intentarlo.
Reemplace el método de acción HttpGet Delete con el código siguiente, que administra los informes de errores.
if (saveChangesError.GetValueOrDefault())
{
ViewData["ErrorMessage"] =
"Delete failed. Try again, and if the problem persists " +
"see your system administrator.";
}
return View(student);
}
Este código acepta un parámetro opcional que indica si se llamó al método después de un error al guardar los
cambios. Este parámetro es false cuando se llama al método HttpGet Delete sin un error anterior. Cuando se
llama por medio del método HttpPost Delete en respuesta a un error de actualización de base de datos, el
parámetro es true y se pasa un mensaje de error a la vista.
El enfoque de primera lectura para HttpPost Delete
Reemplace el método de acción HttpPost Delete (denominado DeleteConfirmed ) con el código siguiente, que
realiza la operación de eliminación y captura los errores de actualización de base de datos.
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var student = await _context.Students
.AsNoTracking()
.SingleOrDefaultAsync(m => m.ID == id);
if (student == null)
{
return RedirectToAction(nameof(Index));
}
try
{
_context.Students.Remove(student);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
return RedirectToAction(nameof(Delete), new { id = id, saveChangesError = true });
}
}
Este código recupera la entidad seleccionada y después llama al método Remove para establecer el estado de la
entidad en Deleted . Cuando se llama a SaveChanges , se genera un comando DELETE de SQL.
El enfoque de crear y adjuntar para HttpPost Delete
Si mejorar el rendimiento de una aplicación de gran volumen es una prioridad, podría evitar una consulta SQL
innecesaria creando instancias de una entidad Student solo con el valor de clave principal y después
estableciendo el estado de la entidad en Deleted . Eso es todo lo que necesita Entity Framework para eliminar la
entidad. (No incluya este código en el proyecto; únicamente se muestra para ilustrar una alternativa).
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
try
{
Student studentToDelete = new Student() { ID = id };
_context.Entry(studentToDelete).State = EntityState.Deleted;
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
return RedirectToAction(nameof(Delete), new { id = id, saveChangesError = true });
}
}
Si la entidad tiene datos relacionados que también se deban eliminar, asegúrese de configurar la eliminación en
cascada en la base de datos. Con este enfoque de eliminación de entidades, es posible que EF no sepa que hay
entidades relacionadas para eliminar.
Actualizar la vista Delete
En Views/Student/Delete.cshtml, agregue un mensaje de error entre los títulos h2 y h3, como se muestra en el
ejemplo siguiente:
<h2>Delete</h2>
<p class="text-danger">@ViewData["ErrorMessage"]</p>
<h3>Are you sure you want to delete this?</h3>
Haga clic en Eliminar. Se mostrará la página de índice sin el estudiante eliminado. (Verá un ejemplo del código
de control de errores en funcionamiento en el tutorial sobre la simultaneidad).
Control de transacciones
De forma predeterminada, Entity Framework implementa las transacciones de manera implícita. En escenarios
donde se realizan cambios en varias filas o tablas, y después se llama a SaveChanges , Entity Framework se
asegura automáticamente de que todos los cambios se realicen correctamente o se produzca un error en todos
ellos. Si primero se realizan algunos cambios y después se produce un error, los cambios se revierten
automáticamente. Para escenarios donde se necesita más control, por ejemplo, si se quieren incluir operaciones
realizadas fuera de Entity Framework en una transacción, vea Transacciones.
Consultas de no seguimiento
Cuando un contexto de base de datos recupera las filas de tabla y crea objetos de entidad que las representa, de
forma predeterminada realiza el seguimiento de si las entidades en memoria están sincronizadas con el
contenido de la base de datos. Los datos en memoria actúan como una caché y se usan cuando se actualiza una
entidad. Este almacenamiento en caché suele ser necesario en una aplicación web porque las instancias de
contexto normalmente son de corta duración (para cada solicitud se crea una y se elimina) y el contexto que lee
una entidad normalmente se elimina antes de volver a usar esa entidad.
Puede deshabilitar el seguimiento de los objetos de entidad en memoria mediante una llamada al método
AsNoTracking . Los siguientes son escenarios típicos en los que es posible que quiera hacer esto:
Durante la vigencia del contexto no es necesario actualizar ninguna entidad ni que EF cargue
automáticamente las propiedades de navegación con las entidades recuperadas por consultas
independientes. Estas condiciones se cumplen frecuentemente en los métodos de acción HttpGet del
controlador.
Se ejecuta una consulta que recupera un gran volumen de datos y solo se actualiza una pequeña parte de
los datos devueltos. Puede ser más eficaz desactivar el seguimiento de la consulta de gran tamaño y
ejecutar una consulta más adelante para las pocas entidades que deban actualizarse.
Se quiere adjuntar una entidad para actualizarla, pero antes se recuperó la misma entidad para un
propósito diferente. Como el contexto de base de datos ya está realizando el seguimiento de la entidad, no
se puede adjuntar la entidad que se quiere cambiar. Una manera de controlar esta situación consiste en
llamar a AsNoTracking en la consulta anterior.
Para obtener más información, vea Tracking vs. No-Tracking (Diferencia entre consultas de seguimiento y no
seguimiento).
Resumen
Ahora tiene un conjunto completo de páginas que realizan sencillas operaciones CRUD para entidades Student.
En el siguiente tutorial podrá expandir la funcionalidad de la página Index mediante la adición de ordenación,
filtrado y paginación.
A N T E R IO R S IG U IE N T E
ASP.NET Core MVC con EF Core: ordenación,
filtrado y paginación (3 de 10)
25/06/2018 • 26 minutes to read • Edit Online
Este código recibe un parámetro sortOrder de la cadena de consulta en la dirección URL. ASP.NET Core MVC
proporciona el valor de la cadena de consulta como un parámetro al método de acción. El parámetro es una
cadena que puede ser "Name" o "Date", seguido (opcionalmente) por un guión bajo y la cadena "desc" para
especificar el orden descendente. El criterio de ordenación predeterminado es el ascendente.
La primera vez que se solicita la página de índice, no hay ninguna cadena de consulta. Los alumnos se muestran
por apellido en orden ascendente, que es el valor predeterminado establecido por el caso desestimado en la
instrucción switch . Cuando el usuario hace clic en un hipervínculo de encabezado de columna, se proporciona el
valor sortOrder correspondiente en la cadena de consulta.
La vista usa los dos elementos ViewData (NameSortParm y DateSortParm) para configurar los hipervínculos del
encabezado de columna con los valores de cadena de consulta adecuados.
Estas son las instrucciones ternarias. La primera de ellas especifica que, si el parámetro sortOrder es NULL o
está vacío, NameSortParm debe establecerse en "name_desc"; en caso contrario, se debe establecer en una
cadena vacía. Estas dos instrucciones habilitan la vista para establecer los hipervínculos de encabezado de
columna de la forma siguiente:
El método usa LINQ to Entities para especificar la columna por la que se va a ordenar. El código crea una variable
IQueryable antes de la instrucción de cambio, la modifica en la instrucción de cambio y llama al método
ToListAsync después de la instrucción switch . Al crear y modificar variables IQueryable , no se envía ninguna
consulta a la base de datos. La consulta no se ejecuta hasta que convierta el objeto IQueryable en una colección
mediante una llamada a un método, como ToListAsync . Por lo tanto, este código produce una única consulta que
no se ejecuta hasta la instrucción return View .
Este código podría detallarse con un gran número de columnas. En el último tutorial de esta serie se muestra
cómo escribir código que le permita pasar el nombre de la columna OrderBy en una variable de cadenas.
Agregar hipervínculos del encabezado de columna a la vista de índice de Student
Reemplace el código de Views/Students/Index.cshtml por el código siguiente para agregar los hipervínculos del
encabezado de columna. Se resaltan las líneas modificadas.
@model IEnumerable<ContosoUniversity.Models.Student>
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
<a asp-action="Index" asp-route-
sortOrder="@ViewData["NameSortParm"]">@Html.DisplayNameFor(model => model.LastName)</a>
</th>
<th>
@Html.DisplayNameFor(model => model.FirstMidName)
</th>
<th>
<a asp-action="Index" asp-route-
sortOrder="@ViewData["DateSortParm"]">@Html.DisplayNameFor(model => model.EnrollmentDate)</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.ID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Este código usa la información que se incluye en las propiedades ViewData para configurar hipervínculos con los
valores de cadena de consulta adecuados.
Ejecute la aplicación, seleccione la ficha Students y haga clic en los encabezados de columna Last Name y
Enrollment Date para comprobar que la ordenación funciona correctamente.
Agregar un cuadro de búsqueda a la página de índice de Students
Para agregar filtrado a la página de índice de Students, agregue un cuadro de texto y un botón de envío a la vista
y haga los cambios correspondientes en el método Index . El cuadro de texto le permite escribir la cadena que
quiera buscar en los campos de nombre y apellido.
Agregar la funcionalidad de filtrado al método Index
En StudentsController.cs, reemplace el método Index por el código siguiente (los cambios se resaltan).
NOTE
Aquí se llama al método Where en un objeto IQueryable y el filtro se procesa en el servidor. En algunos escenarios,
puede hacer una llamada al método Where como un método de extensión en una colección en memoria. (Por ejemplo,
imagine que cambia la referencia a _context.Students , de modo que, en lugar de hacer referencia a EF DbSet , haga
referencia a un método de repositorio que devuelva una colección IEnumerable ). Lo más habitual es que el resultado
fuera el mismo, pero en algunos casos puede ser diferente.
Por ejemplo, la implementación de .NET Framework del método Contains realiza de forma predeterminada una
comparación que diferencia entre mayúsculas y minúsculas, pero en SQL Server se determina por la configuración de
intercalación de la instancia de SQL Server. De forma predeterminada, esta opción de configuración no diferencia entre
mayúsculas y minúsculas. Podría hacer una llamada al método ToUpper para hacer explícitamente que la prueba no
diferenciara entre mayúsculas y minúsculas: donde (s => s.LastName.ToUpper().Contains (searchString.ToUpper()). Esto
garantiza que los resultados permanezcan invariables aunque cambie el código más adelante para usar un repositorio que
devuelva una colección IEnumerable en vez de un objeto IQueryable . (Al hacer una llamada al método Contains en
una colección IEnumerable , obtendrá la implementación de .NET Framework; al hacer una llamada a un objeto
IQueryable , obtendrá la implementación del proveedor de base de datos). En cambio, el rendimiento de esta solución se
ve reducido. El código ToUpper tendría que poner una función en la cláusula WHERE de la instrucción SELECT de TSQL.
Esto impediría que el optimizador usara un índice. Dado que principalmente SQL se instala de forma que no diferencia entre
mayúsculas y minúsculas, es mejor que evite el código ToUpper hasta que migre a un almacén de datos que distinga
entre mayúsculas y minúsculas.
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
Este código usa la aplicación auxiliar de etiquetas <form> para agregar el cuadro de texto de búsqueda y el
botón. De forma predeterminada, la aplicación auxiliar de etiquetas <form> envía datos de formulario con POST,
lo que significa que los parámetros se pasan en el cuerpo del mensaje HTTP y no en la dirección URL como
cadenas de consulta. Al especificar HTTP GET, los datos de formulario se pasan en la dirección URL como
cadenas de consulta, lo que permite que los usuarios marquen la dirección URL. Las directrices de W3C
recomiendan que use GET cuando la acción no produzca ninguna actualización.
Ejecute la aplicación, seleccione la ficha Students, escriba una cadena de búsqueda y haga clic en Search para
comprobar que el filtrado funciona correctamente.
Fíjese en que la dirección URL contiene la cadena de búsqueda.
http://localhost:5813/Students?SearchString=an
Si marca esta página, obtendrá la lista filtrada al usar el marcador. El hecho de agregar method="get" a la etiqueta
form es lo que ha provocado que se generara la cadena de consulta.
En esta fase, si hace clic en un vínculo de ordenación del encabezado de columna, el valor de filtro que especificó
en el cuadro de búsqueda se perderá. Podrá corregirlo en la siguiente sección.
namespace ContosoUniversity
{
public class PaginatedList<T> : List<T>
{
public int PageIndex { get; private set; }
public int TotalPages { get; private set; }
this.AddRange(items);
}
El método CreateAsync en este código toma el tamaño y el número de la página y aplica las instrucciones Skip
y Take correspondientes a IQueryable . Cuando se llama a ToListAsync en IQueryable , devuelve una lista que
solo contiene la página solicitada. Las propiedades HasPreviousPage y HasNextPage se pueden usar para habilitar
o deshabilitar los botones de página Previous y Next.
Para crear el objeto PaginatedList<T> , se usa un método CreateAsync en vez de un constructor, porque los
constructores no pueden ejecutar código asincrónico.
if (searchString != null)
{
page = 1;
}
else
{
searchString = currentFilter;
}
ViewData["CurrentFilter"] = searchString;
int pageSize = 3;
return View(await PaginatedList<Student>.CreateAsync(students.AsNoTracking(), page ?? 1, pageSize));
}
Este código agrega un parámetro de número de página, un parámetro de criterio de ordenación actual y un
parámetro de filtro actual a la firma del método.
La primera vez que se muestra la página, o si el usuario no ha hecho clic en un vínculo de ordenación o
paginación, todos los parámetros son nulos. Si se hace clic en un vínculo de paginación, la variable de página
contiene el número de página que se tiene que mostrar.
El elemento ViewData , denominado CurrentSort, proporciona la vista con el criterio de ordenación actual, que
debe incluirse en los vínculos de paginación para mantener el criterio de ordenación durante la paginación.
El elemento ViewData , denominado CurrentFilter, proporciona la vista con la cadena de filtro actual. Este valor
debe incluirse en los vínculos de paginación para mantener la configuración de filtrado durante la paginación y
debe restaurarse en el cuadro de texto cuando se vuelve a mostrar la página.
Si se cambia la cadena de búsqueda durante la paginación, la página debe restablecerse a 1, porque el nuevo
filtro puede hacer que se muestren diferentes datos. La cadena de búsqueda cambia cuando se escribe un valor
en el cuadro de texto y se presiona el botón Submit. En ese caso, el parámetro searchString no es NULL.
if (searchString != null)
{
page = 1;
}
else
{
searchString = currentFilter;
}
Al final del método Index , el método PaginatedList.CreateAsync convierte la consulta del alumno en una sola
página de alumnos de un tipo de colección que admita la paginación. Entonces, esa única página de alumnos
pasa a la vista.
El método PaginatedList.CreateAsync toma un número de página. Los dos signos de interrogación representan
el operador de uso combinado de NULL. El operador de uso combinado de NULL define un valor
predeterminado para un tipo que acepta valores NULL; la expresión (page ?? 1) devuelve el valor de page si
tiene algún valor o devuelve 1 si page es NULL.
@model PaginatedList<ContosoUniversity.Models.Student>
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
<a asp-action="Index" asp-route-sortOrder="@ViewData["NameSortParm"]" asp-route-
<a asp-action="Index" asp-route-sortOrder="@ViewData["NameSortParm"]" asp-route-
currentFilter="@ViewData["CurrentFilter"]">Last Name</a>
</th>
<th>
First Name
</th>
<th>
<a asp-action="Index" asp-route-sortOrder="@ViewData["DateSortParm"]" asp-route-
currentFilter="@ViewData["CurrentFilter"]">Enrollment Date</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.ID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
@{
var prevDisabled = !Model.HasPreviousPage ? "disabled" : "";
var nextDisabled = !Model.HasNextPage ? "disabled" : "";
}
<a asp-action="Index"
asp-route-sortOrder="@ViewData["CurrentSort"]"
asp-route-page="@(Model.PageIndex - 1)"
asp-route-currentFilter="@ViewData["CurrentFilter"]"
class="btn btn-default @prevDisabled">
Previous
</a>
<a asp-action="Index"
asp-route-sortOrder="@ViewData["CurrentSort"]"
asp-route-page="@(Model.PageIndex + 1)"
asp-route-currentFilter="@ViewData["CurrentFilter"]"
class="btn btn-default @nextDisabled">
Next
</a>
La instrucción @model de la parte superior de la página especifica que ahora la vista obtiene un objeto
PaginatedList<T> en lugar de un objeto List<T> .
Los vínculos del encabezado de la columna usan la cadena de consulta para pasar la cadena de búsqueda actual
al controlador, de modo que el usuario pueda ordenar los resultados del filtro:
Haga clic en los vínculos de paginación en distintos criterios de ordenación para comprobar que la paginación
funciona correctamente. A continuación, escriba una cadena de búsqueda e intente llevar a cabo la paginación de
nuevo, para comprobar que la paginación también funciona correctamente con filtrado y ordenación.
namespace ContosoUniversity.Models.SchoolViewModels
{
public class EnrollmentDateGroup
{
[DataType(DataType.Date)]
public DateTime? EnrollmentDate { get; set; }
using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Data;
using ContosoUniversity.Models.SchoolViewModels;
Agregue una variable de clase para el contexto de base de datos inmediatamente después de la llave de apertura
para la clase y obtenga una instancia del contexto de ASP.NET Core DI:
La instrucción LINQ agrupa las entidades de alumnos por fecha de inscripción, calcula la cantidad de entidades
que se incluyen en cada grupo y almacena los resultados en una colección de objetos de modelo de la vista
EnrollmentDateGroup .
NOTE
En la versión 1.0 de Entity Framework Core, el conjunto de resultados completo se devuelve al cliente y la agrupación se
realiza en el cliente. En algunos casos, esto puede crear problemas de rendimiento. Asegúrese de probar el rendimiento con
volúmenes de producción de datos y, si es necesario, use SQL sin formato para realizar la agrupación en el servidor. Para
obtener información sobre cómo usar SQL sin formato, consulte el último tutorial de esta serie.
@model IEnumerable<ContosoUniversity.Models.SchoolViewModels.EnrollmentDateGroup>
@{
ViewData["Title"] = "Student Body Statistics";
}
<table>
<tr>
<th>
Enrollment Date
</th>
<th>
Students
</th>
</tr>
Ejecute la aplicación y vaya a la página About. En una tabla se muestra el número de alumnos para cada fecha de
inscripción.
Resumen
En este tutorial, ha visto cómo realizar la ordenación, el filtrado, la paginación y la agrupación. En el siguiente
tutorial, aprenderá a controlar los cambios en el modelo de datos mediante migraciones.
A N T E R IO R S IG U IE N T E
ASP.NET Core MVC con EF Core: Migraciones (4 de
10)
25/06/2018 • 14 minutes to read • Edit Online
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
</ItemGroup>
(Los números de versión en este ejemplo eran los actuales cuando se escribió el tutorial).
{
"ConnectionStrings": {
"DefaultConnection": "Server=
(localdb)\\mssqllocaldb;Database=ContosoUniversity2;Trusted_Connection=True;MultipleActiveResultSets=true"
},
Este cambio configura el proyecto para que la primera migración cree una base de datos. Esto no es necesario
para comenzar a usar las migraciones, pero más adelante se verá por qué es una buena idea.
NOTE
Como alternativa a cambiar el nombre de la base de datos, puede eliminar la base de datos. Use el Explorador de
objetos de SQL Server (SSOX) o el comando de la CLI database drop :
info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0]
User profile is available. Using 'C:\Users\username\AppData\Local\ASP.NET\DataProtection-Keys' as key
repository and Windows DPAPI to encrypt keys at rest.
info: Microsoft.EntityFrameworkCore.Infrastructure[100403]
Entity Framework Core 2.0.0-rtm-26452 initialized 'SchoolContext' using provider
'Microsoft.EntityFrameworkCore.SqlServer' with options: None
Done. To undo this action, use 'ef migrations remove'
NOTE
Si ve un mensaje de error No se encuentra ningún archivo ejecutable que coincida con el comando "dotnet-ef", vea esta
entrada de blog para obtener ayuda para solucionar problemas.
Si ve un mensaje de error "No se puede obtener acceso al archivo... ContosoUniversity.dll porque lo está
usando otro proceso.", busque el icono de IIS Express en la bandeja del sistema de Windows, haga clic con el
botón derecho en él y, después, haga clic en ContosoUniversity > Detener sitio.
Las migraciones llaman al método Up para implementar los cambios del modelo de datos para una migración.
Cuando se escribe un comando para revertir la actualización, las migraciones llaman al método Down .
Este código es para la migración inicial que se creó cuando se escribió el comando
migrations add InitialCreate . El parámetro de nombre de la migración ("InitialCreate" en el ejemplo) se usa
para el nombre de archivo y puede ser lo que quiera. Es más recomendable elegir una palabra o frase que
resuma lo que se hace en la migración. Por ejemplo, podría denominar "AddDepartmentTable" a una migración
posterior.
Si creó la migración inicial cuando la base de datos ya existía, se genera el código de creación de la base de
datos pero no es necesario ejecutarlo porque la base de datos ya coincide con el modelo de datos. Al
implementar la aplicación en otro entorno donde la base de datos todavía no existe, se ejecutará este código
para crear la base de datos, por lo que es recomendable probarlo primero. Por ese motivo se cambió antes el
nombre de la base de datos en la cadena de conexión, para que las migraciones puedan crear uno desde cero.
El resultado del comando es similar al comando migrations add , con la excepción de que verá registros para
los comandos SQL que configuran la base de datos. La mayoría de los registros se omite en la siguiente salida
de ejemplo. Si prefiere no ver este nivel de detalle en los mensajes de registro, puede cambiarlo en el archivo
appsettings.Development.json. Para obtener más información, vea Introducción al registro.
info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0]
User profile is available. Using 'C:\Users\username\AppData\Local\ASP.NET\DataProtection-Keys' as key
repository and Windows DPAPI to encrypt keys at rest.
info: Microsoft.EntityFrameworkCore.Infrastructure[100403]
Entity Framework Core 2.0.0-rtm-26452 initialized 'SchoolContext' using provider
'Microsoft.EntityFrameworkCore.SqlServer' with options: None
info: Microsoft.EntityFrameworkCore.Database.Command[200101]
Executed DbCommand (467ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
CREATE DATABASE [ContosoUniversity2];
info: Microsoft.EntityFrameworkCore.Database.Command[200101]
Executed DbCommand (20ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE [__EFMigrationsHistory] (
[MigrationId] nvarchar(150) NOT NULL,
[ProductVersion] nvarchar(32) NOT NULL,
CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId])
);
info: Microsoft.EntityFrameworkCore.Database.Command[200101]
Executed DbCommand (3ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20170816151242_InitialCreate', N'2.0.0-rtm-26452');
Done.
Use el Explorador de objetos de SQL Server para inspeccionar la base de datos como hizo en el primer
tutorial. Observará la adición de una tabla __EFMigrationsHistory que realiza el seguimiento de las migraciones
que se han aplicado a la base de datos. Si examina los datos de esa tabla, verá una fila para la primera
migración. (En el último registro del ejemplo de salida de la CLI anterior se muestra la instrucción INSERT que
crea esta fila).
Ejecute la aplicación para comprobar que todo funciona igual que antes.
Diferencias entre la interfaz de la línea de comandos (CLI) y la
Consola del Administrador de paquetes (PMC)
Las herramientas de EF para la administración de migraciones están disponibles desde los comandos de la CLI
de .NET Core o los cmdlets de PowerShell en la ventana Consola del Administrador de paquetes (PMC ) de
Visual Studio. En este tutorial se muestra cómo usar la CLI, pero puede usar la PMC si lo prefiere.
Los comandos de EF para los comandos de la PMC están en el paquete Microsoft.EntityFrameworkCore.Tools.
Este paquete ya está incluido en el metapaquete Microsoft.AspNetCore.All, por lo que no es necesario
instalarlo.
Importante: Este no es el mismo paquete que el que se instala para la CLI mediante la edición del archivo
.csproj. El nombre de este paquete termina en Tools , a diferencia del nombre de paquete de la CLI que termina
en Tools.DotNet .
Para obtener más información sobre los comandos de la CLI, vea CLI de .NET Core.
Para obtener más información sobre los comandos de la PMC, vea Consola del Administrador de paquetes
(Visual Studio).
Resumen
En este tutorial, ha visto cómo crear y aplicar la primera migración. En el siguiente, comenzará examinando
temas más avanzados expandiendo el modelo de datos. Por el camino, podrá crear y aplicar migraciones
adicionales.
A N T E R IO R S IG U IE N T E
ASP.NET Core MVC con EF Core: Modelo de datos
(5 de 10)
25/06/2018 • 53 minutes to read • Edit Online
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }
El atributo DataType se usa para especificar un tipo de datos más específico que el tipo intrínseco de base de
datos. En este caso solo se quiere realizar el seguimiento de la fecha, no de la fecha y la hora. La enumeración
DataType proporciona muchos tipos de datos, como Date ( Fecha), Time ( Hora), PhoneNumber ( Número de
teléfono), Currency (Divisa), EmailAddress (Dirección de correo electrónico) y muchos más. El atributo DataType
también puede permitir que la aplicación proporcione automáticamente características específicas del tipo. Por
ejemplo, se puede crear un vínculo mailto: para DataType.EmailAddress y se puede proporcionar un selector de
datos para DataType.Date en exploradores compatibles con HTML5. El atributo DataType emite atributos
data- de HTML 5 (se pronuncia "datos dash") que los exploradores HTML 5 pueden comprender. Los atributos
DataType no proporcionan ninguna validación.
El valor ApplyFormatInEditMode especifica que el formato se debe aplicar también cuando el valor se muestra en
un cuadro de texto para su edición. (Es posible que no le interese ese comportamiento para algunos campos, por
ejemplo, para los valores de divisa, es posible que no quiera que el símbolo de la divisa se incluya en el cuadro
de texto editable).
Se puede usar el atributo DisplayFormat por sí solo, pero normalmente se recomienda usar también el atributo
DataType . El atributo DataType transmite la semántica de los datos en contraposición a cómo se representan en
una pantalla y ofrece las siguientes ventajas que no proporciona DisplayFormat :
El explorador puede habilitar características de HTML5 (por ejemplo, para mostrar un control de
calendario, el símbolo de divisa adecuado según la configuración regional, vínculos de correo electrónico,
validación de entradas del lado cliente, etc.).
De manera predeterminada, el explorador representa los datos con el formato correcto según la
configuración regional.
Para obtener más información, vea la documentación de la aplicación auxiliar de etiquetas <entrada>.
Ejecute la aplicación, vaya a la página Students Index y verá que ya no se muestran las horas para las fechas de
inscripción. Lo mismo sucede para cualquier vista en la que se use el modelo Student.
El atributo StringLength
También puede especificar reglas de validación de datos y mensajes de error de validación mediante atributos. El
atributo StringLength establece la longitud máxima de la base de datos y proporciona la validación del lado
cliente y el lado servidor para ASP.NET MVC. En este atributo también se puede especificar la longitud mínima
de la cadena, pero el valor mínimo no influye en el esquema de la base de datos.
Imagine que quiere asegurarse de que los usuarios no escriban más de 50 caracteres para un nombre. Para
agregar esta limitación, agregue atributos StringLength a las propiedades LastName y FirstMidName , como se
muestra en el ejemplo siguiente:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[StringLength(50)]
public string LastName { get; set; }
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }
El atributo StringLength no impedirá que un usuario escriba un espacio en blanco para un nombre. Puede usar
el atributo RegularExpression para aplicar restricciones a la entrada. Por ejemplo, el código siguiente requiere
que el primer carácter sea una letra mayúscula y el resto de caracteres sean alfabéticos:
[RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")]
El atributo MaxLength proporciona una funcionalidad similar a la del atributo StringLength pero no proporciona
la validación del lado cliente.
Ahora el modelo de base de datos ha cambiado de tal forma que se requiere un cambio en el esquema de la
base de datos. Deberá usar migraciones para actualizar el esquema sin perder los datos que pueda haber
agregado a la base de datos mediante la interfaz de usuario de la aplicación.
Guarde los cambios y compile el proyecto. Después, abra la ventana de comandos en la carpeta de proyecto y
escriba los comandos siguientes:
El comando migrations add advierte de que se puede producir pérdida de datos, porque el cambio reduce la
longitud máxima para dos columnas. Las migraciones crean un archivo denominado
<marca_de_tiempo>_MaxLengthOnNames.cs. Este archivo contiene código en el método Up que actualizará la
base de datos para que coincida con el modelo de datos actual. El comando database update ejecutó ese código.
Entity Framework usa la marca de tiempo que precede al nombre de archivo de migraciones para ordenar las
migraciones. Puede crear varias migraciones antes de ejecutar el comando de actualización de bases de datos y,
después, todas las migraciones se aplican en el orden en el que se hayan creado.
Ejecute la aplicación, haga clic en la pestaña Students, haga clic en Create New (Crear) y escriba cualquier
nombre de más de 50 caracteres. Al hacer clic en Create (Crear), la validación del lado cliente muestra un
mensaje de error.
El atributo Column
También puede usar atributos para controlar cómo se asignan las clases y propiedades a la base de datos.
Imagine que hubiera usado el nombre FirstMidName para el nombre de campo por la posibilidad de que el
campo contenga también un segundo nombre. Pero quiere que la columna de base de datos se denomine
FirstName , ya que los usuarios que van a escribir consultas ad hoc en la base de datos están acostumbrados a
ese nombre. Para realizar esta asignación, puede usar el atributo Column .
El atributo Column especifica que, cuando se cree la base de datos, la columna de la tabla Student que se asigna
a la propiedad FirstMidName se denominará FirstName . En otras palabras, cuando el código hace referencia a
Student.FirstMidName , los datos procederán o se actualizarán en la columna FirstName de la tabla Student . Si
no especifica nombres de columna, se les asigna el mismo nombre que el de la propiedad.
En el archivo Student.cs, agregue una instrucción using para System.ComponentModel.DataAnnotations.Schema y
agregue el atributo de nombre de columna a la propiedad FirstMidName , como se muestra en el código
resaltado siguiente:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[StringLength(50)]
public string LastName { get; set; }
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
[Column("FirstName")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }
La adición del atributo Column cambia el modelo de respaldo de SchoolContext , por lo que no coincidirá con la
base de datos.
Guarde los cambios y compile el proyecto. Después, abra la ventana de comandos en la carpeta de proyecto y
escriba los comandos siguientes para crear otra migración:
En el Explorador de objetos de SQL Server, abra el diseñador de tablas de Student haciendo doble clic en la
tabla Student.
Antes de aplicar las dos primeras migraciones, las columnas de nombre eran de tipo nvarchar(MAX). Ahora son
de tipo nvarchar(50) y el nombre de columna ha cambiado de FirstMidName a FirstName.
NOTE
Si intenta compilar antes de terminar de crear todas las clases de entidad en las secciones siguientes, es posible que se
produzcan errores del compilador.
En Models/Student.cs, reemplace el código que agregó anteriormente con el código siguiente. Los cambios
aparecen resaltados.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
[Column("FirstName")]
[Display(Name = "First Name")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Enrollment Date")]
public DateTime EnrollmentDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}
El atributo Required
El atributo Required hace que las propiedades de nombre sean campos obligatorios. El atributo Required no es
necesario para los tipos que no aceptan valores NULL, como los tipos de valor (DateTime, int, double, float, etc.).
Los tipos que no aceptan valores NULL se tratan automáticamente como campos obligatorios.
Puede quitar el atributo Required y reemplazarlo por un parámetro de longitud mínima para el atributo
StringLength :
El atributo Display
El atributo Display especifica que el título de los cuadros de texto debe ser "First Name" (Nombre), "Last
Name" (Apellidos), "Full Name" (Nombre completo) y "Enrollment Date" (Fecha de inscripción) en lugar del
nombre de propiedad de cada instancia (que no tiene ningún espacio para dividir las palabras).
La propiedad calculada FullName
FullName es una propiedad calculada que devuelve un valor que se crea mediante la concatenación de otras dos
propiedades. Por tanto, solo tiene un descriptor de acceso get y no se generará ninguna columna FullName en la
base de datos.
namespace ContosoUniversity.Models
{
public class Instructor
{
public int ID { get; set; }
[Required]
[Display(Name = "Last Name")]
[StringLength(50)]
public string LastName { get; set; }
[Required]
[Column("FirstName")]
[Display(Name = "First Name")]
[StringLength(50)]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Hire Date")]
public DateTime HireDate { get; set; }
Tenga en cuenta que varias propiedades son las mismas en las entidades Instructor y Student. En el tutorial
Implementación de la herencia más adelante en esta serie, deberá refactorizar este código para eliminar la
redundancia.
Puede colocar varios atributos en una línea, por lo que también puede escribir los atributos HireDate como se
indica a continuación:
Un instructor puede impartir cualquier número de cursos, por lo que CourseAssignments se define como una
colección.
Si una propiedad de navegación puede contener varias entidades, su tipo debe ser una lista a la que se puedan
agregar, eliminar y actualizar entradas. Puede especificar ICollection<T> o un tipo como List<T> o HashSet<T> .
Si especifica ICollection<T> , EF crea una colección HashSet<T> de forma predeterminada.
El motivo por el que se trata de entidades CourseAssignment se explica a continuación, en la sección sobre
relaciones de varios a varios.
Las reglas de negocio de Contoso University establecen que un instructor solo puede tener una oficina a lo
sumo, por lo que la propiedad OfficeAssignment contiene una única entidad OfficeAssignment (que puede ser
NULL si no se asigna ninguna oficina).
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class OfficeAssignment
{
[Key]
public int InstructorID { get; set; }
[StringLength(50)]
[Display(Name = "Office Location")]
public string Location { get; set; }
El atributo Key
Hay una relación de uno a cero o uno entre las entidades Instructor y OfficeAssignment. Solo existe una
asignación de oficina en relación con el instructor al que se asigna y, por tanto, su clave principal también es su
clave externa para la entidad Instructor. Pero Entity Framework no reconoce automáticamente InstructorID
como la clave principal de esta entidad porque su nombre no sigue la convención de nomenclatura de ID o
classnameID. Por tanto, se usa el atributo Key para identificarla como la clave:
[Key]
public int InstructorID { get; set; }
También puede usar el atributo Key si la entidad tiene su propia clave principal, pero querrá asignar un nombre
a la propiedad que no sea classnameID o ID.
De forma predeterminada, EF trata la clave como no generada por la base de datos porque la columna es para
una relación de identificación.
La propiedad de navegación Instructor
La entidad Instructor tiene una propiedad de navegación OfficeAssignment que acepta valores NULL (porque es
posible que no se asigne una oficina a un instructor), y la entidad OfficeAssignment tiene una propiedad de
navegación Instructor que no acepta valores NULL (porque una asignación de oficina no puede existir sin un
instructor; InstructorID no acepta valores NULL ). Cuando una entidad Instructor tiene una entidad
OfficeAssignment relacionada, cada entidad tendrá una referencia a la otra en su propiedad de navegación.
Podría incluir un atributo [Required] en la propiedad de navegación de Instructor para especificar que debe
haber un instructor relacionado, pero no es necesario hacerlo porque la clave externa InstructorID (que
también es la clave para esta tabla) no acepta valores NULL.
En Models/Course.cs, reemplace el código que agregó anteriormente con el código siguiente. Los cambios
aparecen resaltados.
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
[Range(0, 5)]
public int Credits { get; set; }
La entidad Course tiene una propiedad de clave externa DepartmentID que señala a la entidad Department
relacionada y tiene una propiedad de navegación Department .
Entity Framework no requiere que agregue una propiedad de clave externa al modelo de datos cuando tenga
una propiedad de navegación para una entidad relacionada. EF crea automáticamente claves externas en la base
de datos siempre que se necesiten y crea propiedades reemplazadas para ellas. Pero tener la clave externa en el
modelo de datos puede hacer que las actualizaciones sean más sencillas y eficaces. Por ejemplo, al recuperar una
entidad Course para modificarla, la entidad Department es NULL si no la carga, por lo que cuando se actualiza
la entidad Course, deberá capturar primero la entidad Department. Cuando la propiedad de clave externa
DepartmentID se incluye en el modelo de datos, no es necesario capturar la entidad Department antes de
actualizar.
El atributo DatabaseGenerated
El atributo DatabaseGenerated con el parámetro None en la propiedad CourseID especifica que los valores de
clave principal los proporciona el usuario, en lugar de que los genere la base de datos.
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
De forma predeterminada, Entity Framework da por supuesto que la base de datos genera los valores de clave
principal. Es lo que le interesa en la mayoría de los escenarios. Pero para las entidades Course, usará un número
de curso especificado por el usuario como una serie 1000 para un departamento, una serie 2000 para otro y así
sucesivamente.
También se puede usar el atributo DatabaseGenerated para generar valores predeterminados, como en el caso de
las columnas de base de datos que se usan para registrar la fecha de creación o actualización de una fila. Para
obtener más información, vea Propiedades generadas.
Propiedades de clave externa y de navegación
Las propiedades de clave externa y las de navegación de la entidad Course reflejan las relaciones siguientes:
Un curso se asigna a un departamento, por lo que hay una clave externa DepartmentID y una propiedad de
navegación Department por las razones mencionadas anteriormente.
Un curso puede tener cualquier número de alumnos inscritos en él, por lo que la propiedad de navegación
Enrollments es una colección:
Un curso puede ser impartido por varios instructores, por lo que la propiedad de navegación CourseAssignments
es una colección (el tipo CourseAssignment se explica más adelante):
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Department
{
public int DepartmentID { get; set; }
[DataType(DataType.Currency)]
[Column(TypeName = "money")]
public decimal Budget { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Start Date")]
public DateTime StartDate { get; set; }
El atributo Column
Anteriormente usó el atributo Column para cambiar la asignación de nombres de columna. En el código de la
entidad Department, se usa el atributo Column para cambiar la asignación de tipos de datos de SQL para que la
columna se defina con el tipo de moneda de SQL Server en la base de datos:
[Column(TypeName="money")]
public decimal Budget { get; set; }
Por lo general, la asignación de columnas no es necesaria, dado que Entity Framework elige el tipo de datos de
SQL Server adecuado en función del tipo CLR que se defina para la propiedad. El tipo CLR decimal se asigna a
un tipo decimal de SQL Server. Pero en este caso se sabe que la columna va a contener cantidades de divisa, y
el tipo de datos de divisa es más adecuado para eso.
Propiedades de clave externa y de navegación
Las propiedades de clave externa y de navegación reflejan las relaciones siguientes:
Un departamento puede tener o no un administrador, y un administrador es siempre un instructor. Por tanto, la
propiedad InstructorID se incluye como la clave externa de la entidad Instructor y se agrega un signo de
interrogación después de la designación del tipo int para marcar la propiedad como que acepta valores NULL.
La propiedad de navegación se denomina Administrator pero contiene una entidad Instructor:
Un departamento puede tener varios cursos, por lo que hay una propiedad de navegación Courses:
public ICollection<Course> Courses { get; set; }
NOTE
Por convención, Entity Framework permite la eliminación en cascada para las claves externas que no aceptan valores NULL
y para las relaciones de varios a varios. Esto puede dar lugar a reglas de eliminación en cascada circulares, lo que producirá
una excepción al intentar agregar una migración. Por ejemplo, si no definió la propiedad Department.InstructorID como
que acepta valores NULL, EF podría configurar una regla de eliminación en cascada para eliminar el instructor cuando se
elimine el departamento, que no es lo que quiere que ocurra. Si las reglas de negocio requerían que la propiedad
InstructorID no acepte valores NULL, tendría que usar la siguiente instrucción de la API fluida para deshabilitar la
eliminación en cascada en la relación:
modelBuilder.Entity<Department>()
.HasOne(d => d.Administrator)
.WithMany()
.OnDelete(DeleteBehavior.Restrict)
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}
Un registro de inscripción es para un solo estudiante, por lo que hay una propiedad de clave externa StudentID
y una propiedad de navegación Student :
Cada línea de relación tiene un 1 en un extremo y un asterisco (*) en el otro, para indicar una relación uno a
varios.
Si la tabla Enrollment no incluyera información de calificaciones, solo tendría que contener las dos claves
externas CourseID y StudentID. En ese caso, sería una tabla de combinación de varios a varios sin carga (o una
tabla de combinación pura) en la base de datos. Las entidades Instructor y Course tienen ese tipo de relación de
varios a varios, y el paso siguiente consiste en crear una clase de entidad para que funcione como una tabla de
combinación sin carga.
(EF 6.x es compatible con las tablas de combinación implícitas para relaciones de varios a varios, pero EF Core
no. Para obtener más información, vea la explicación en el repositorio de GitHub EF Core).
La entidad CourseAssignment
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class CourseAssignment
{
public int InstructorID { get; set; }
public int CourseID { get; set; }
public Instructor Instructor { get; set; }
public Course Course { get; set; }
}
}
Clave compuesta
Puesto que las claves externas no aceptan valores NULL y juntas identifican de forma única a cada fila de la
tabla, una clave principal independiente no es necesaria. Las propiedades InstructorID y CourseID deben
funcionar como una clave principal compuesta. La única manera de identificar claves principales compuestas
para EF es mediante la API fluida (no se puede realizar mediante el uso de atributos). En la sección siguiente
verá cómo configurar la clave principal compuesta.
La clave compuesta garantiza que, aunque es posible tener varias filas para un curso y varias filas para un
instructor, no se pueden tener varias filas para el mismo instructor y curso. La entidad de combinación
Enrollment define su propia clave principal, por lo que este tipo de duplicados son posibles. Para evitar estos
duplicados, podría agregar un índice único en los campos de clave externa o configurar Enrollment con una
clave principal compuesta similar a CourseAssignment . Para obtener más información, vea Índices.
Actualizar el contexto de base de datos
Agregue el código resaltado siguiente al archivo Data/SchoolContext.cs:
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
modelBuilder.Entity<CourseAssignment>()
.HasKey(c => new { c.CourseID, c.InstructorID });
}
}
}
Este código agrega las nuevas entidades y configura la clave principal compuesta de la entidad
CourseAssignment.
En este tutorial, solo se usa la API fluida para la asignación de base de datos que no se puede realizar con
atributos. Pero también se puede usar la API fluida para especificar casi todas las reglas de formato, validación y
asignación que se pueden realizar mediante el uso de atributos. Algunos atributos como MinimumLength no se
pueden aplicar con la API fluida. Como se mencionó anteriormente, MinimumLength no cambia el esquema, solo
aplica una regla de validación del lado cliente y del lado servidor.
Algunos desarrolladores prefieren usar la API fluida exclusivamente para mantener "limpias" las clases de
entidad. Si quiere, puede mezclar atributos y la API fluida, y hay algunas personalizaciones que solo se pueden
realizar mediante la API fluida, pero en general el procedimiento recomendado es elegir uno de estos dos
enfoques y usarlo de forma constante siempre que sea posible. Si usa ambos enfoques, tenga en cuenta que
siempre que hay un conflicto, la API fluida invalida los atributos.
Para obtener más información sobre la diferencia entre los atributos y la API fluida, vea Métodos de
configuración.
Además de las líneas de relación uno a varios (1 a *), aquí se puede ver la línea de relación de uno a cero o uno
(1 a 0..1) entre las entidades Instructor y OfficeAssignment, y la línea de relación de cero o uno a varios (0..1 a *)
entre las entidades Instructor y Department.
namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
//context.Database.EnsureCreated();
Como vimos en el primer tutorial, la mayor parte de este código simplemente crea objetos de entidad y carga
los datos de ejemplo en propiedades según sea necesario para las pruebas. Tenga en cuenta cómo se
administran las relaciones de varios a varios: el código crea relaciones mediante la creación de entidades en los
conjuntos de entidades de combinación Enrollments y CourseAssignment .
Si ahora intentara ejecutar el comando database update (no lo haga todavía), obtendría el error siguiente:
En ocasiones, al ejecutar migraciones con datos existentes, debe insertar código auxiliar de los datos en la base
de datos para satisfacer las restricciones de clave externa. El código generado en el método Up agrega a la tabla
Course una clave externa DepartmentID que no acepta valores NULL. Si ya hay filas en la tabla Course cuando
se ejecuta el código, se produce un error en la operación AddColumn porque SQL Server no sabe qué valor
incluir en la columna que no puede ser NULL. En este tutorial se va a ejecutar la migración en una base de datos
nueva, pero en una aplicación de producción la migración tendría que controlar los datos existentes, por lo que
en las instrucciones siguientes se muestra un ejemplo de cómo hacerlo.
Para realizar este trabajo de migración con datos existentes, tendrá que cambiar el código para asignar un valor
predeterminado a la nueva columna y crear un departamento de código auxiliar denominado "Temp" para que
actúe como el predeterminado. Como resultado, las filas Course existentes estarán relacionadas con el
departamento "Temp" después de ejecutar el método Up .
Abra el archivo {marca_de_tiempo }_ComplexDataModel.cs.
Convierta en comentario la línea de código que agrega la columna DepartmentID a la tabla Course.
migrationBuilder.AlterColumn<string>(
name: "Title",
table: "Course",
maxLength: 50,
nullable: true,
oldClrType: typeof(string),
oldNullable: true);
//migrationBuilder.AddColumn<int>(
// name: "DepartmentID",
// table: "Course",
// nullable: false,
// defaultValue: 0);
Agregue el código resaltado siguiente después del código que crea la tabla Department:
migrationBuilder.CreateTable(
name: "Department",
columns: table => new
{
DepartmentID = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy",
SqlServerValueGenerationStrategy.IdentityColumn),
Budget = table.Column<decimal>(type: "money", nullable: false),
InstructorID = table.Column<int>(nullable: true),
Name = table.Column<string>(maxLength: 50, nullable: true),
StartDate = table.Column<DateTime>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Department", x => x.DepartmentID);
table.ForeignKey(
name: "FK_Department_Instructor_InstructorID",
column: x => x.InstructorID,
principalTable: "Instructor",
principalColumn: "ID",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.AddColumn<int>(
name: "DepartmentID",
table: "Course",
nullable: false,
defaultValue: 1);
En una aplicación de producción, debería escribir código o scripts para agregar filas Department y filas Course
relacionadas a las nuevas filas Department. Después, ya no necesitaría el departamento "Temp" o el valor
predeterminado en la columna Course.DepartmentID.
Guarde los cambios y compile el proyecto.
{
"ConnectionStrings": {
"DefaultConnection": "Server=
(localdb)\\mssqllocaldb;Database=ContosoUniversity3;Trusted_Connection=True;MultipleActiveResultSets=true"
},
Después de cambiar el nombre de la base de datos o de eliminarla, ejecute el comando database update en la
ventana de comandos para ejecutar las migraciones.
Ejecute la aplicación para que el método DbInitializer.Initialize ejecute y rellene la base de datos nueva.
Abra la base de datos en SSOX como hizo anteriormente y expanda el nodo Tablas para ver que se han creado
todas las tablas. (Si SSOX sigue abierto de la vez anterior, haga clic en el botón Actualizar).
A N T E R IO R S IG U IE N T E
ASP.NET Core MVC con EF Core: Lectura de datos
relacionados (6 de 10)
25/06/2018 • 26 minutes to read • Edit Online
Puede recuperar algunos de los datos en distintas consultas y EF "corregirá" las propiedades de
navegación. Es decir, EF agrega automáticamente las entidades recuperadas por separado que pertenecen
a propiedades de navegación de entidades recuperadas previamente. Para la consulta que recupera los
datos relacionados, puede usar el método Load en lugar de un método que devuelva una lista o un
objeto, como ToList o Single .
Carga explícita. Cuando la entidad se lee por primera vez, no se recuperan datos relacionados. Se escribe
código que recupera los datos relacionados si son necesarios. Como en el caso de la carga diligente con
consultas independientes, la carga explícita da como resultado varias consultas que se envían a la base de
datos. La diferencia es que con la carga explícita el código especifica las propiedades de navegación que
se van a cargar. En Entity Framework Core 1.1 se puede usar el método Load para realizar la carga
explícita. Por ejemplo:
Carga diferida. Cuando la entidad se lee por primera vez, no se recuperan datos relacionados. Pero la
primera vez que intente obtener acceso a una propiedad de navegación, se recuperan automáticamente
los datos necesarios para esa propiedad de navegación. Cada vez que intente obtener datos de una
propiedad de navegación por primera vez, se envía una consulta a la base de datos. Entity Framework
Core 1.0 no admite la carga diferida.
Consideraciones sobre el rendimiento
Si sabe que necesita datos relacionados para cada entidad que se recupere, la carga diligente suele ofrecer el
mejor rendimiento, dado que una sola consulta que se envía a la base de datos normalmente es más eficaz que
consultas independientes para cada entidad recuperada. Por ejemplo, suponga que cada departamento tiene
diez cursos relacionados. Con la carga diligente de todos los datos relacionados se crearía una única consulta
sencilla (de combinación) y un único recorrido de ida y vuelta a la base de datos. Una consulta independiente
para los cursos de cada departamento crearía 11 recorridos de ida y vuelta a la base de datos. Los recorridos de
ida y vuelta adicionales a la base de datos afectan especialmente de forma negativa al rendimiento cuando la
latencia es alta.
Por otro lado, en algunos escenarios, las consultas independientes son más eficaces. Es posible que la carga
diligente de todos los datos relacionados en una consulta genere una combinación muy compleja que SQL
Server no pueda procesar eficazmente. O bien, si necesita tener acceso a las propiedades de navegación de una
entidad solo para un subconjunto de un conjunto de las entidades que está procesando, es posible que las
consultas independientes den mejores resultados porque la carga diligente de todo el contenido por adelantado
recuperaría más datos de los que necesita. Si el rendimiento es crítico, es mejor probarlo de ambas formas para
elegir la mejor opción.
Cree un controlador denominado CoursesController para el tipo de entidad Course, con las mismas opciones
para el proveedor de scaffolding Controlador de MVC con vistas que usan Entity Framework que usó
anteriormente para el controlador de Students, como se muestra en la ilustración siguiente:
Abra Views/Courses/Index.cshtml y reemplace el código de plantilla con el código siguiente. Se resaltan los
cambios:
@model IEnumerable<ContosoUniversity.Models.Course>
@{
ViewData["Title"] = "Courses";
}
<h2>Courses</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.CourseID)
</th>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Credits)
</th>
<th>
@Html.DisplayNameFor(model => model.Department)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.CourseID)
</td>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Credits)
</td>
<td>
@Html.DisplayFor(modelItem => item.Department.Name)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.CourseID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.CourseID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.CourseID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Ejecute la aplicación y haga clic en la pestaña Courses para ver la lista con los nombres de departamento.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ContosoUniversity.Models.SchoolViewModels
{
public class InstructorIndexData
{
public IEnumerable<Instructor> Instructors { get; set; }
public IEnumerable<Course> Courses { get; set; }
public IEnumerable<Enrollment> Enrollments { get; set; }
}
}
Abra InstructorsController.cs y agregue una instrucción using para el espacio de nombres ViewModels:
using ContosoUniversity.Models.SchoolViewModels;
Reemplace el método Index con el código siguiente para realizar la carga diligente de los datos relacionados y
colocarlos en el modelo de vista.
public async Task<IActionResult> Index(int? id, int? courseID)
{
var viewModel = new InstructorIndexData();
viewModel.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
if (id != null)
{
ViewData["InstructorID"] = id.Value;
Instructor instructor = viewModel.Instructors.Where(
i => i.ID == id.Value).Single();
viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
}
if (courseID != null)
{
ViewData["CourseID"] = courseID.Value;
viewModel.Enrollments = viewModel.Courses.Where(
x => x.CourseID == courseID).Single().Enrollments;
}
return View(viewModel);
}
El método acepta datos de ruta opcionales ( id ) y un parámetro de cadena de consulta ( courseID ) que
proporcionan los valores ID del instructor y el curso seleccionados. Los parámetros se proporcionan mediante
los hipervínculos Select de la página.
El código comienza creando una instancia del modelo de vista y coloca en ella la lista de instructores. El código
especifica la carga diligente para Instructor.OfficeAssignment y las propiedades de navegación de
Instructor.CourseAssignments . Dentro de la propiedad CourseAssignments se carga la propiedad Course y
dentro de esta se cargan las propiedades Enrollments y Department , y dentro de cada entidad Enrollment se
carga la propiedad Student .
Como la vista siempre requiere la entidad OfficeAssignment, resulta más eficaz capturarla en la misma consulta.
Las entidades Course son necesarias cuando se selecciona un instructor en la página web, por lo que una sola
consulta es más adecuada que varias solo si la página se muestra con más frecuencia con un curso seleccionado
que sin él.
El código repite CourseAssignments y Course porque se necesitan dos propiedades de Course . En la primera
cadena de llamadas ThenInclude se obtiene CourseAssignment.Course , Course.Enrollments y
Enrollment.Student .
En ese punto del código, otro elemento ThenInclude sería para las propiedades de navegación de Student , lo
que no es necesario. Pero la llamada a Include se inicia con las propiedades de Instructor , por lo que tendrá
que volver a pasar por la cadena, especificando esta vez Course.Department en lugar de Course.Enrollments .
if (id != null)
{
ViewData["InstructorID"] = id.Value;
Instructor instructor = viewModel.Instructors.Where(
i => i.ID == id.Value).Single();
viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
}
El método Where devuelve una colección, pero en este caso los criterios que se pasan a ese método dan como
resultado que solo se devuelva una entidad Instructor. El método Single convierte la colección en una única
entidad Instructor, que proporciona acceso a la propiedad CourseAssignments de esa entidad. La propiedad
CourseAssignments contiene entidades CourseAssignment , de las que solo quiere las entidades Course
relacionadas.
El método Single se usa en una colección cuando se sabe que la colección tendrá un único elemento. El método
Single inicia una excepción si la colección que se pasa está vacía o si hay más de un elemento. Una alternativa es
SingleOrDefault , que devuelve una valor predeterminado ( NULL, en este caso) si la colección está vacía. Pero en
este caso, eso seguiría iniciando una excepción (al tratar de buscar una propiedad Courses en una referencia
nula), y el mensaje de excepción indicaría con menos claridad la causa del problema. Cuando se llama al método
Single , también se puede pasar la condición Where en lugar de llamar al método Where por separado:
En lugar de:
if (courseID != null)
{
ViewData["CourseID"] = courseID.Value;
viewModel.Enrollments = viewModel.Courses.Where(
x => x.CourseID == courseID).Single().Enrollments;
}
@{
ViewData["Title"] = "Instructors";
}
<h2>Instructors</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>Last Name</th>
<th>First Name</th>
<th>Hire Date</th>
<th>Office</th>
<th>Courses</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Instructors)
{
string selectedRow = "";
if (item.ID == (int?)ViewData["InstructorID"])
{
selectedRow = "success";
}
<tr class="@selectedRow">
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.HireDate)
</td>
<td>
@if (item.OfficeAssignment != null)
{
@item.OfficeAssignment.Location
}
</td>
<td>
@{
foreach (var course in item.CourseAssignments)
{
@course.Course.CourseID @: @course.Course.Title <br />
}
}
</td>
<td>
<a asp-action="Index" asp-route-id="@item.ID">Select</a> |
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.ID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Se ha agregado una columna Courses en la que se muestran los cursos que imparte cada instructor. Vea
Transición de línea explícita con @: para obtener más información sobre esta sintaxis de Razor.
Ha agregado código que agrega dinámicamente class="success" al elemento tr del instructor
seleccionado. Esto establece el color de fondo de la fila seleccionada mediante una clase de arranque.
Se ha agregado un hipervínculo nuevo con la etiqueta Select inmediatamente antes de los otros vínculos
de cada fila, lo que hace que el identificador del instructor seleccionado se envíe al método Index .
Ejecute la aplicación y haga clic en la pestaña Instructors. En la página se muestra la propiedad Location de las
entidades OfficeAssignment relacionadas y una celda de tabla vacía cuando no hay ninguna entidad
OfficeAssignment relacionada.
En el archivo Views/Instructors/Index.cshtml, después del elemento de tabla de cierre (situado al final del
archivo), agregue el código siguiente. Este código muestra una lista de cursos relacionados con un instructor
cuando se selecciona un instructor.
</table>
}
Este código lee la propiedad Courses del modelo de vista para mostrar una lista de cursos. También
proporciona un hipervínculo Select que envía el identificador del curso seleccionado al método de acción
Index .
Actualice la página y seleccione un instructor. Ahora verá una cuadrícula en la que se muestran los cursos
asignados al instructor seleccionado, y para cada curso, el nombre del departamento asignado.
Después del bloque de código que se acaba de agregar, agregue el código siguiente. Esto muestra una lista de
los estudiantes que están inscritos en un curso cuando se selecciona ese curso.
Este código lee la propiedad Enrollments del modelo de vista para mostrar una lista de los estudiantes inscritos
en el curso.
Vuelva a actualizar la página y seleccione un instructor. Después, seleccione un curso para ver la lista de los
estudiantes inscritos y sus calificaciones.
Carga explícita
Cuando se recuperó la lista de instructores en InstructorsController.cs, se especificó la carga diligente de la
propiedad de navegación CourseAssignments .
Suponga que esperaba que los usuarios rara vez quisieran ver las inscripciones en un instructor y curso
seleccionados. En ese caso, es posible que quiera cargar los datos de inscripción solo si se solicitan. Para ver un
ejemplo de cómo realizar la carga explícita, reemplace el método Index con el código siguiente, que quita la
carga diligente de Enrollments y carga explícitamente esa propiedad. Los cambios de código aparecen
resaltados.
public async Task<IActionResult> Index(int? id, int? courseID)
{
var viewModel = new InstructorIndexData();
viewModel.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.OrderBy(i => i.LastName)
.ToListAsync();
if (id != null)
{
ViewData["InstructorID"] = id.Value;
Instructor instructor = viewModel.Instructors.Where(
i => i.ID == id.Value).Single();
viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
}
if (courseID != null)
{
ViewData["CourseID"] = courseID.Value;
var selectedCourse = viewModel.Courses.Where(x => x.CourseID == courseID).Single();
await _context.Entry(selectedCourse).Collection(x => x.Enrollments).LoadAsync();
foreach (Enrollment enrollment in selectedCourse.Enrollments)
{
await _context.Entry(enrollment).Reference(x => x.Student).LoadAsync();
}
viewModel.Enrollments = selectedCourse.Enrollments;
}
return View(viewModel);
}
El nuevo código quita las llamadas al método ThenInclude para los datos de inscripción del código que recupera
las entidades Instructor. Si se seleccionan un instructor y un curso, el código resaltado recupera las entidades
Enrollment para el curso seleccionado y las entidades Student de cada inscripción.
Ejecute la aplicación, vaya a la página de índice de instructores ahora y no verá ninguna diferencia en lo que se
muestra en la página, aunque haya cambiado la forma en que se recuperan los datos.
Resumen
Ha usado la carga diligente con una consulta y con varias para leer datos relacionados en las propiedades de
navegación. En el siguiente tutorial, obtendrá información sobre cómo actualizar datos relacionados.
A N T E R IO R S IG U IE N T E
ASP.NET Core MVC con EF Core: Actualización de
datos relacionados (7 de 10)
25/06/2018 • 31 minutes to read • Edit Online
if (await TryUpdateModelAsync<Course>(courseToUpdate,
"",
c => c.Credits, c => c.DepartmentID, c => c.Title))
{
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
return RedirectToAction(nameof(Index));
}
PopulateDepartmentsDropDownList(courseToUpdate.DepartmentID);
return View(courseToUpdate);
}
Después del método HttpPost de Edit , cree un método que cargue la información de departamento para la lista
desplegable.
El método PopulateDepartmentsDropDownList obtiene una lista de todos los departamentos ordenados por
nombre, crea una colección SelectList para obtener una lista desplegable y pasa la colección a la vista en
ViewBag . El método acepta el parámetro opcional selectedDepartment , que permite al código que realiza la
llamada especificar el elemento que se seleccionará cuando se procese la lista desplegable. La vista pasará el
nombre "DepartmentID" a la aplicación auxiliar de etiquetas <select> , y luego la aplicación auxiliar sabe que
puede buscar en el objeto ViewBag una SelectList denominada "DepartmentID".
El método Create de HttpGet llama al método PopulateDepartmentsDropDownList sin configurar el elemento
seleccionado, ya que el departamento todavía no está establecido para un nuevo curso:
Los métodos HttpPost para Create y Edit también incluyen código que configura el elemento seleccionado
cuando vuelven a mostrar la página después de un error. Esto garantiza que, cuando vuelve a aparecer la página
para mostrar el mensaje de error, el departamento que se haya seleccionado permanece seleccionado.
Agregar AsNoTracking a los métodos Details y Delete
Para optimizar el rendimiento de las páginas Course Details y Delete, agregue llamadas AsNoTracking en los
métodos Details y Delete de HttpGet.
return View(course);
}
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}
return View(course);
}
<div class="form-group">
<label asp-for="Department" class="control-label"></label>
<select asp-for="DepartmentID" class="form-control" asp-items="ViewBag.DepartmentID">
<option value="">-- Select Department --</option>
</select>
<span asp-validation-for="DepartmentID" class="text-danger" />
<div class="form-group">
<label asp-for="CourseID" class="control-label"></label>
<div>@Html.DisplayFor(model => model.CourseID)</div>
</div>
Ya hay un campo oculto ( <input type="hidden"> ) para el número de curso en la vista Edit. Agregar una aplicación
auxiliar de etiquetas <label> no elimina la necesidad de un campo oculto, porque no hace que el número de
curso se incluya en los datos enviados cuando el usuario hace clic en Save en la página Edit.
En Views/Courses/Delete.cshtml, agregue un campo de número de curso en la parte superior y cambie el
identificador del departamento por el nombre del departamento.
@model ContosoUniversity.Models.Course
@{
ViewData["Title"] = "Delete";
}
<h2>Delete</h2>
<form asp-action="Delete">
<div class="form-actions no-color">
<input type="submit" value="Delete" class="btn btn-default" /> |
<a asp-action="Index">Back to List</a>
</div>
</form>
</div>
Reemplace el método Edit de HttpPost con el siguiente código para controlar las actualizaciones de
asignaciones de oficina:
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditPost(int? id)
{
if (id == null)
{
return NotFound();
}
if (await TryUpdateModelAsync<Instructor>(
instructorToUpdate,
"",
i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
{
if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
return RedirectToAction(nameof(Index));
}
return View(instructorToUpdate);
}
if (await TryUpdateModelAsync<Instructor>(
instructorToUpdate,
"",
i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
<div class="form-group">
<label asp-for="OfficeAssignment.Location" class="control-label"></label>
<input asp-for="OfficeAssignment.Location" class="form-control" />
<span asp-validation-for="OfficeAssignment.Location" class="text-danger" />
</div>
Ejecute la aplicación, seleccione la pestaña Instructors y, después, haga clic en Edit en un instructor. Cambie el
valor de Office Location y haga clic en Save.
Agregar asignaciones de cursos a la página Edit de los instructores
Los instructores pueden impartir cualquier número de cursos. Ahora mejorará la página Edit Instructor al agregar
la capacidad de cambiar las asignaciones de cursos mediante un grupo de casillas, tal y como se muestra en la
siguiente captura de pantalla:
La relación entre las entidades Course e Instructor es varios a varios. Para agregar y eliminar relaciones, agregue
y quite entidades del conjunto de entidades combinadas CourseAssignments.
La interfaz de usuario que le permite cambiar los cursos a los que está asignado un instructor es un grupo de
casillas. Se muestra una casilla para cada curso en la base de datos y se seleccionan aquellos a los que está
asignado actualmente el instructor. El usuario puede activar o desactivar las casillas para cambiar las asignaciones
de cursos. Si el número de cursos fuera mucho mayor, probablemente tendría que usar un método diferente de
presentar los datos en la vista, pero usaría el mismo método de manipulación de una entidad de combinación
para crear o eliminar relaciones.
Actualizar el controlador de Instructors
Para proporcionar datos a la vista de la lista de casillas, deberá usar una clase de modelo de vista.
Cree AssignedCourseData.cs en la carpeta SchoolViewModels y reemplace el código existente con el código
siguiente:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ContosoUniversity.Models.SchoolViewModels
{
public class AssignedCourseData
{
public int CourseID { get; set; }
public string Title { get; set; }
public bool Assigned { get; set; }
}
}
En InstructorsController.cs, reemplace el método Edit de HttpGet por el código siguiente. Los cambios aparecen
resaltados.
El código agrega carga diligente para la propiedad de navegación Courses y llama al método
PopulateAssignedCourseData nuevo para proporcionar información de la matriz de casilla mediante la clase de
modelo de vista AssignedCourseData .
El código en el método PopulateAssignedCourseData lee a través de todas las entidades Course para cargar una
lista de cursos mediante la clase de modelo de vista. Para cada curso, el código comprueba si existe el curso en la
propiedad de navegación Courses del instructor. Para crear una búsqueda eficaz al comprobar si un curso está
asignado al instructor, los cursos asignados a él se colocan en una colección HashSet . La propiedad Assigned
está establecida en true para los cursos a los que está asignado el instructor. La vista usará esta propiedad para
determinar qué casilla debe mostrarse como seleccionada. Por último, la lista se pasa a la vista en ViewData .
A continuación, agregue el código que se ejecuta cuando el usuario hace clic en Save. Reemplace el método
EditPost con el siguiente código y agregue un nuevo método que actualiza la propiedad de navegación Courses
de la entidad Instructor.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int? id, string[] selectedCourses)
{
if (id == null)
{
return NotFound();
}
if (await TryUpdateModelAsync<Instructor>(
instructorToUpdate,
"",
i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
{
if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
UpdateInstructorCourses(selectedCourses, instructorToUpdate);
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
return RedirectToAction(nameof(Index));
}
UpdateInstructorCourses(selectedCourses, instructorToUpdate);
PopulateAssignedCourseData(instructorToUpdate);
return View(instructorToUpdate);
}
private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
if (selectedCourses == null)
{
instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
return;
}
if (instructorCourses.Contains(course.CourseID))
{
CourseAssignment courseToRemove = instructorToUpdate.CourseAssignments.SingleOrDefault(i =>
i.CourseID == course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}
La firma del método ahora es diferente del método Edit de HttpGet, por lo que el nombre del método cambia
de EditPost a Edit .
Puesto que la vista no tiene una colección de entidades Course, el enlazador de modelos no puede actualizar
automáticamente la propiedad de navegación CourseAssignments . En lugar de usar el enlazador de modelos para
actualizar la propiedad de navegación CourseAssignments , lo hace en el nuevo método UpdateInstructorCourses .
Por lo tanto, tendrá que excluir la propiedad CourseAssignments del enlace de modelos. Esto no requiere ningún
cambio en el código que llama a TryUpdateModel porque está usando la sobrecarga de la creación de listas de
permitidos y CourseAssignments no se encuentra en la lista de inclusión.
Si no se ha seleccionado ninguna casilla, el código en UpdateInstructorCourses inicializa la propiedad de
navegación CourseAssignments con una colección vacía y devuelve:
private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
if (selectedCourses == null)
{
instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
return;
}
if (instructorCourses.Contains(course.CourseID))
{
CourseAssignment courseToRemove = instructorToUpdate.CourseAssignments.SingleOrDefault(i =>
i.CourseID == course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}
A continuación, el código recorre en bucle todos los cursos de la base de datos y coteja los que están asignados
actualmente al instructor frente a los que se han seleccionado en la vista. Para facilitar las búsquedas eficaces,
estas dos últimas colecciones se almacenan en objetos HashSet .
Si se ha activado la casilla para un curso pero este no se encuentra en la propiedad de navegación
Instructor.CourseAssignments , el curso se agrega a la colección en la propiedad de navegación.
private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
if (selectedCourses == null)
{
instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
return;
}
if (instructorCourses.Contains(course.CourseID))
{
CourseAssignment courseToRemove = instructorToUpdate.CourseAssignments.SingleOrDefault(i =>
i.CourseID == course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}
if (instructorCourses.Contains(course.CourseID))
{
CourseAssignment courseToRemove = instructorToUpdate.CourseAssignments.SingleOrDefault(i =>
i.CourseID == course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}
NOTE
Al pegar el código en Visual Studio, se cambiarán los saltos de línea de tal forma que el código se interrumpe. Presione
Ctrl+Z una vez para deshacer el formato automático. Esto corregirá los saltos de línea para que se muestren como se ven
aquí. No es necesario que la sangría sea perfecta, pero las líneas @</tr><tr> , @:<td> , @:</td> y @:</tr> deben estar
en una única línea tal y como se muestra, de lo contrario, obtendrá un error en tiempo de ejecución. Con el bloque de
código nuevo seleccionado, presione tres veces la tecla TAB para alinearlo con el código existente. Puede comprobar el
estado de este problema aquí.
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<table>
<tr>
@{
int cnt = 0;
List<ContosoUniversity.Models.SchoolViewModels.AssignedCourseData> courses =
ViewBag.Courses;
Este código crea una tabla HTML que tiene tres columnas. En cada columna hay una casilla seguida de un título
que está formado por el número y el título del curso. Todas las casillas tienen el mismo nombre
("selectedCourses"), que informa al enlazador de modelos que se deben tratar como un grupo. El atributo de
valor de cada casilla se establece en el valor de CourseID . Cuando se envía la página, el enlazador de modelos
pasa una matriz al controlador formada solo por los valores CourseID de las casillas activadas.
Cuando las casillas se representan inicialmente, aquellas que son para cursos asignados al instructor tienen
atributos seleccionados, lo que las selecciona (las muestra activadas).
Ejecute la aplicación, seleccione la pestaña Instructors y haga clic en Edit en un instructor para ver la página
Edit.
Cambie algunas asignaciones de cursos y haga clic en Save. Los cambios que haga se reflejan en la página de
índice.
NOTE
El enfoque que se aplica aquí para modificar datos de los cursos del instructor funciona bien cuando hay un número
limitado de cursos. Para las colecciones que son mucho más grandes, se necesitaría una interfaz de usuario y un método de
actualización diferentes.
_context.Instructors.Remove(instructor);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
// POST: Instructors/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("FirstMidName,HireDate,LastName,OfficeAssignment")] Instructor
instructor, string[] selectedCourses)
{
if (selectedCourses != null)
{
instructor.CourseAssignments = new List<CourseAssignment>();
foreach (var course in selectedCourses)
{
var courseToAdd = new CourseAssignment { InstructorID = instructor.ID, CourseID =
int.Parse(course) };
instructor.CourseAssignments.Add(courseToAdd);
}
}
if (ModelState.IsValid)
{
_context.Add(instructor);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
PopulateAssignedCourseData(instructor);
return View(instructor);
}
Este código es similar a lo que ha visto para los métodos Edit , excepto que no hay cursos seleccionados
inicialmente. El método Create de HttpGet no llama al método PopulateAssignedCourseData porque pueda haber
cursos seleccionados sino para proporcionar una colección vacía para el bucle foreach en la vista (en caso
contrario, el código de vista podría producir una excepción de referencia nula).
El método Create de HttpPost agrega cada curso seleccionado a la propiedad de navegación CourseAssignments
antes de comprobar si hay errores de validación y agrega el instructor nuevo a la base de datos. Los cursos se
agregan incluso si hay errores de modelo, por lo que cuando hay errores del modelo (por ejemplo, el usuario
escribió una fecha no válida) y se vuelve a abrir la página con un mensaje de error, las selecciones de cursos que
se habían realizado se restauran todas automáticamente.
Tenga en cuenta que, para poder agregar cursos a la propiedad de navegación CourseAssignments , debe inicializar
la propiedad como una colección vacía:
Como alternativa a hacerlo en el código de control, podría hacerlo en el modelo de Instructor cambiando el
captador de propiedad para que cree automáticamente la colección si no existe, como se muestra en el ejemplo
siguiente:
private ICollection<CourseAssignment> _courseAssignments;
public ICollection<CourseAssignment> CourseAssignments
{
get
{
return _courseAssignments ?? (_courseAssignments = new List<CourseAssignment>());
}
set
{
_courseAssignments = value;
}
}
Si modifica la propiedad CourseAssignments de esta manera, puede quitar el código de inicialización de propiedad
explícito del controlador.
En Views/Instructor/Create.cshtml, agregue un cuadro de texto de la ubicación de la oficina y casillas para cursos
antes del botón Submit. Al igual que en el caso de la página Edit, corrija el formato si Visual Studio vuelve a
aplicar formato al código al pegarlo.
<div class="form-group">
<label asp-for="OfficeAssignment.Location" class="control-label"></label>
<input asp-for="OfficeAssignment.Location" class="form-control" />
<span asp-validation-for="OfficeAssignment.Location" class="text-danger" />
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<table>
<tr>
@{
int cnt = 0;
List<ContosoUniversity.Models.SchoolViewModels.AssignedCourseData> courses =
ViewBag.Courses;
Control de transacciones
Como se explicó en el tutorial de CRUD, Entity Framework implementa las transacciones de manera implícita.
Para escenarios donde se necesita más control, por ejemplo, si se quieren incluir operaciones realizadas fuera de
Entity Framework en una transacción, vea Transacciones.
Resumen
Ahora ha completado la introducción para trabajar con datos relacionados. En el siguiente tutorial verá cómo
tratar los conflictos de simultaneidad.
A N T E R IO R S IG U IE N T E
ASP.NET Core MVC con EF Core: Simultaneidad (8
de 10)
25/06/2018 • 33 minutes to read • Edit Online
Antes de que Jane haga clic en Save, John visita la misma página y cambia el campo Start Date de 9/1/2007 a
9/1/2013.
Jane hace clic en Save primero y ve su cambio cuando el explorador vuelve a la página de índice.
Entonces, John hace clic en Save en una página Edit que sigue mostrando un presupuesto de 350.000,00 USD.
Lo que sucede después viene determinado por cómo controla los conflictos de simultaneidad.
Algunas de las opciones se exponen a continuación:
Puede realizar un seguimiento de la propiedad que ha modificado un usuario y actualizar solo las
columnas correspondientes de la base de datos.
En el escenario de ejemplo, no se perdería ningún dato porque los dos usuarios actualizaron diferentes
propiedades. La próxima vez que un usuario examine el departamento de inglés, verá los cambios tanto de
Jane como de John: una fecha de inicio de 9/1/2013 y un presupuesto de cero dólares. Este método de
actualización puede reducir el número de conflictos que pueden dar lugar a una pérdida de datos, pero no
puede evitar la pérdida de datos si se realizan cambios paralelos a la misma propiedad de una entidad. Si
Entity Framework funciona de esta manera o no, depende de cómo implemente el código de actualización.
A menudo no resulta práctico en una aplicación web, porque puede requerir mantener grandes cantidades
de estado con el fin de realizar un seguimiento de todos los valores de propiedad originales de una
entidad, así como los valores nuevos. Mantener grandes cantidades de estado puede afectar al
rendimiento de la aplicación porque requiere recursos del servidor o se deben incluir en la propia página
web (por ejemplo, en campos ocultos) o en una cookie.
Puede permitir que los cambios de John sobrescriban los cambios de Jane.
La próxima vez que un usuario examine el departamento de inglés, verá 9/1/2013 y el valor de 350.000,00
USD restaurado. Esto se denomina un escenario de Prevalece el cliente o Prevalece el último. (Todos los
valores del cliente tienen prioridad sobre lo que aparece en el almacén de datos). Como se mencionó en la
introducción de esta sección, si no hace ninguna codificación para el control de la simultaneidad, se
realizará automáticamente.
Puede evitar que el cambio de John se actualice en la base de datos.
Por lo general, debería mostrar un mensaje de error, mostrarle el estado actual de los datos y permitirle
volver a aplicar los cambios si quiere realizarlos. Esto se denomina un escenario de Prevalece el almacén.
(Los valores del almacén de datos tienen prioridad sobre los valores enviados por el cliente). En este
tutorial implementará el escenario de Prevalece el almacén. Este método garantiza que ningún cambio se
sobrescriba sin que se avise al usuario de lo que está sucediendo.
Detectar los conflictos de simultaneidad
Puede resolver los conflictos controlando las excepciones DbConcurrencyException que inicia Entity Framework.
Para saber cuándo se producen dichas excepciones, Entity Framework debe ser capaz de detectar conflictos. Por
lo tanto, debe configurar correctamente la base de datos y el modelo de datos. Algunas opciones para habilitar la
detección de conflictos son las siguientes:
En la tabla de la base de datos, incluya una columna de seguimiento que pueda usarse para determinar si
una fila ha cambiado. Después puede configurar Entity Framework para que incluya esa columna en la
cláusula Where de los comandos Update o Delete de SQL.
El tipo de datos de la columna de seguimiento suele ser rowversion . El valor rowversion es un número
secuencial que se incrementa cada vez que se actualiza la fila. En un comando Update o Delete, la cláusula
Where incluye el valor original de la columna de seguimiento (la versión de la fila original). Si otro usuario
ha cambiado la fila que se está actualizando, el valor en la columna rowversion es diferente del valor
original, por lo que la instrucción Update o Delete no puede encontrar la fila que se va a actualizar debido
a la cláusula Where. Cuando Entity Framework encuentra que no se ha actualizado ninguna fila mediante
el comando Update o Delete (es decir, cuando el número de filas afectadas es cero), lo interpreta como un
conflicto de simultaneidad.
Configure Entity Framework para que incluya los valores originales de cada columna de la tabla en la
cláusula Where de los comandos Update y Delete.
Como se muestra en la primera opción, si algo en la fila ha cambiado desde que se leyó por primera, la
cláusula Where no devolverá una fila para actualizar, lo cual Entity Framework interpreta como un conflicto
de simultaneidad. Para las tablas de base de datos que tienen muchas columnas, este enfoque puede dar
lugar a cláusulas Where muy grandes y puede requerir mantener grandes cantidades de estado. Tal y
como se indicó anteriormente, el mantenimiento de grandes cantidades de estado puede afectar al
rendimiento de la aplicación. Por tanto, generalmente este enfoque no se recomienda y no es el método
usado en este tutorial.
Si quiere implementar este enfoque para la simultaneidad, tendrá que marcar todas las propiedades de
clave no principal de la entidad de la que quiere realizar un seguimiento de simultaneidad agregándoles el
atributo ConcurrencyCheck . Este cambio permite que Entity Framework incluya todas las columnas en la
cláusula Where de SQL de las instrucciones Update y Delete.
En el resto de este tutorial agregará una propiedad de seguimiento rowversion para la entidad Department,
creará un controlador y vistas, y comprobará que todo funciona correctamente.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Department
{
public int DepartmentID { get; set; }
[DataType(DataType.Currency)]
[Column(TypeName = "money")]
public decimal Budget { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Start Date")]
public DateTime StartDate { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
El atributo Timestamp especifica que esta columna se incluirá en la cláusula Where de los comandos Update y
Delete enviados a la base de datos. El atributo se denomina Timestamp porque las versiones anteriores de SQL
Server usaban un tipo de datos timestamp antes de que la rowversion de SQL lo sustituyera por otro. El tipo
.NET de rowversion es una matriz de bytes.
Si prefiere usar la API fluida, puede usar el método IsConcurrencyToken (en Data/SchoolContext.cs) para
especificar la propiedad de seguimiento, tal como se muestra en el ejemplo siguiente:
modelBuilder.Entity<Department>()
.Property(p => p.RowVersion).IsConcurrencyToken();
Al agregar una propiedad cambió el modelo de base de datos, por lo que necesita realizar otra migración.
Guarde los cambios, compile el proyecto y, después, escriba los siguientes comandos en la ventana de comandos:
En el archivo DepartmentsController.cs, cambie las cuatro repeticiones de "FirstMidName" a "FullName" para que
las listas desplegables del administrador del departamento contengan el nombre completo del instructor en lugar
de simplemente el apellido.
@{
ViewData["Title"] = "Departments";
}
<h2>Departments</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Name)
</th>
<th>
@Html.DisplayNameFor(model => model.Budget)
</th>
<th>
@Html.DisplayNameFor(model => model.StartDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Administrator)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Budget)
</td>
<td>
@Html.DisplayFor(modelItem => item.StartDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Administrator.FullName)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.DepartmentID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.DepartmentID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.DepartmentID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Esto cambia el encabezado por "Departments", elimina la columna RowVersion y muestra el nombre completo en
lugar del nombre del administrador.
Sustituya el código existente para el método Edit de HttpPost por el siguiente código:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int? id, byte[] rowVersion)
{
if (id == null)
{
return NotFound();
}
if (departmentToUpdate == null)
{
Department deletedDepartment = new Department();
await TryUpdateModelAsync(deletedDepartment);
ModelState.AddModelError(string.Empty,
"Unable to save changes. The department was deleted by another user.");
ViewData["InstructorID"] = new SelectList(_context.Instructors, "ID", "FullName",
deletedDepartment.InstructorID);
return View(deletedDepartment);
}
_context.Entry(departmentToUpdate).Property("RowVersion").OriginalValue = rowVersion;
if (await TryUpdateModelAsync<Department>(
departmentToUpdate,
"",
s => s.Name, s => s.StartDate, s => s.Budget, s => s.InstructorID))
{
try
{
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
catch (DbUpdateConcurrencyException ex)
{
var exceptionEntry = ex.Entries.Single();
var clientValues = (Department)exceptionEntry.Entity;
var databaseEntry = exceptionEntry.GetDatabaseValues();
if (databaseEntry == null)
{
ModelState.AddModelError(string.Empty,
"Unable to save changes. The department was deleted by another user.");
}
else
{
var databaseValues = (Department)databaseEntry.ToObject();
if (databaseValues.Name != clientValues.Name)
{
ModelState.AddModelError("Name", $"Current value: {databaseValues.Name}");
}
if (databaseValues.Budget != clientValues.Budget)
{
ModelState.AddModelError("Budget", $"Current value: {databaseValues.Budget:c}");
}
if (databaseValues.StartDate != clientValues.StartDate)
{
ModelState.AddModelError("StartDate", $"Current value: {databaseValues.StartDate:d}");
}
if (databaseValues.InstructorID != clientValues.InstructorID)
{
Instructor databaseInstructor = await _context.Instructors.SingleOrDefaultAsync(i => i.ID
== databaseValues.InstructorID);
ModelState.AddModelError("InstructorID", $"Current value:
{databaseInstructor?.FullName}");
}
El código comienza por intentar leer el departamento que se va a actualizar. Si el método SingleOrDefaultAsync
devuelve NULL, otro usuario eliminó el departamento. En ese caso, el código usa los valores del formulario
expuesto para crear una entidad Department, por lo que puede volver a mostrarse la página Edit con un mensaje
de error. Como alternativa, no tendrá que volver a crear la entidad Department si solo muestra un mensaje de
error sin volver a mostrar los campos del departamento.
La vista almacena el valor RowVersion original en un campo oculto, y este método recibe ese valor en el
parámetro rowVersion . Antes de llamar a SaveChanges , tendrá que colocar dicho valor de propiedad RowVersion
original en la colección OriginalValues para la entidad.
_context.Entry(departmentToUpdate).Property("RowVersion").OriginalValue = rowVersion;
Cuando Entity Framework crea un comando UPDATE de SQL, ese comando incluirá una cláusula WHERE que
comprueba si hay una fila que tenga el valor RowVersion original. Si no hay ninguna fila afectada por el comando
UPDATE (ninguna fila tiene el valor RowVersion original), Entity Framework inicia una excepción
DbUpdateConcurrencyException .
El código del bloque catch de esa excepción obtiene la entidad Department afectada que tiene los valores
actualizados de la propiedad Entries del objeto de excepción.
La colección Entries contará con un solo objeto EntityEntry . Puede usar dicho objeto para obtener los nuevos
valores especificados por el usuario y los valores actuales de la base de datos.
El código agrega un mensaje de error personalizado para cada columna que tenga valores de base de datos
diferentes de lo que el usuario especificó en la página Edit (aquí solo se muestra un campo por razones de
brevedad).
var databaseValues = (Department)databaseEntry.ToObject();
if (databaseValues.Name != clientValues.Name)
{
ModelState.AddModelError("Name", $"Current value: {databaseValues.Name}");
Por último, el código establece el valor RowVersion de departmentToUpdate para el nuevo valor recuperado de la
base de datos. Este nuevo valor RowVersion se almacenará en el campo oculto cuando se vuelva a mostrar la
página Edit y, la próxima vez que el usuario haga clic en Save, solo se detectarán los errores de simultaneidad
que se produzcan desde que se vuelva a mostrar la página Edit.
departmentToUpdate.RowVersion = (byte[])databaseValues.RowVersion;
ModelState.Remove("RowVersion");
La instrucción ModelState.Remove es necesaria porque ModelState tiene el valor RowVersion antiguo. En la vista,
el valor ModelState de un campo tiene prioridad sobre los valores de propiedad de modelo cuando ambos están
presentes.
@{
ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>Department</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="DepartmentID" />
<input type="hidden" asp-for="RowVersion" />
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Budget" class="control-label"></label>
<input asp-for="Budget" class="form-control" />
<span asp-validation-for="Budget" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="StartDate" class="control-label"></label>
<input asp-for="StartDate" class="form-control" />
<span asp-validation-for="StartDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="InstructorID" class="control-label"></label>
<select asp-for="InstructorID" class="form-control" asp-items="ViewBag.InstructorID">
<option value="">-- Select Administrator --</option>
</select>
<span asp-validation-for="InstructorID" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
if (concurrencyError.GetValueOrDefault())
{
ViewData["ConcurrencyErrorMessage"] = "The record you attempted to delete "
+ "was modified by another user after you got the original values. "
+ "The delete operation was canceled and the current values in the "
+ "database have been displayed. If you still want to delete this "
+ "record, click the Delete button again. Otherwise "
+ "click the Back to List hyperlink.";
}
return View(department);
}
El método acepta un parámetro opcional que indica si la página volverá a aparecer después de un error de
simultaneidad. Si esta marca es true y el departamento especificado ya no existe, significa que otro usuario lo
eliminó. En ese caso, el código redirige a la página de índice. Si esta marca es true y el departamento existe,
significa que otro usuario lo modificó. En ese caso, el código envía un mensaje de error a la vista mediante
ViewData .
Reemplace el código en el método Delete de HttpPost (denominado DeleteConfirmed ) con el código siguiente:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Delete(Department department)
{
try
{
if (await _context.Departments.AnyAsync(m => m.DepartmentID == department.DepartmentID))
{
_context.Departments.Remove(department);
await _context.SaveChangesAsync();
}
return RedirectToAction(nameof(Index));
}
catch (DbUpdateConcurrencyException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
return RedirectToAction(nameof(Delete), new { concurrencyError = true, id = department.DepartmentID
});
}
}
En el código al que se aplicó la técnica scaffolding que acaba de reemplazar, este método solo acepta un
identificador de registro:
Ha cambiado este parámetro en una instancia de la entidad Department creada por el enlazador de modelos.
Esto proporciona a EF acceso al valor de la propiedad RowVersion, además de la clave de registro.
También ha cambiado el nombre del método de acción de DeleteConfirmed a Delete . El código al que se aplicó
la técnica scaffolding usa el nombre DeleteConfirmed para proporcionar al método HttpPost una firma única. (El
CLR requiere métodos sobrecargados para tener parámetros de método diferentes). Ahora que las firmas son
únicas, puede ceñirse a la convención MVC y usar el mismo nombre para los métodos de eliminación de
HttpPost y HttpGet.
Si ya se ha eliminado el departamento, el método AnyAsync devuelve false y la aplicación simplemente vuelve al
método de índice.
Si se detecta un error de simultaneidad, el código vuelve a mostrar la página de confirmación de Delete y
proporciona una marca que indica que se debería mostrar un mensaje de error de simultaneidad.
Actualizar la vista Delete
En Views/Departments/Delete.cshtml, reemplace el código al que se aplicó la técnica scaffolding con el siguiente
código, que agrega un campo de mensaje de error y campos ocultos para las propiedades DepartmentID y
RowVersion. Los cambios aparecen resaltados.
@model ContosoUniversity.Models.Department
@{
ViewData["Title"] = "Delete";
}
<h2>Delete</h2>
<p class="text-danger">@ViewData["ConcurrencyErrorMessage"]</p>
<form asp-action="Delete">
<input type="hidden" asp-for="DepartmentID" />
<input type="hidden" asp-for="RowVersion" />
<div class="form-actions no-color">
<input type="submit" value="Delete" class="btn btn-default" /> |
<a asp-action="Index">Back to List</a>
</div>
</form>
</div>
Ejecute la aplicación y vaya a la página de índice de Departments. Haga clic con el botón derecho en el
hipervínculo Delete del departamento de inglés, seleccione Abrir en nueva pestaña y, después, en la primera
pestaña, haga clic en el hipervínculo Edit del departamento de inglés.
En la primera ventana, cambie uno de los valores y haga clic en Save:
En la segunda pestaña, haga clic en Delete. Verá el mensaje de error de simultaneidad y se actualizarán los
valores de Department con lo que está actualmente en la base de datos.
Si vuelve a hacer clic en Delete, se le redirigirá a la página de índice, que muestra que se ha eliminado el
departamento.
@{
ViewData["Title"] = "Details";
}
<h2>Details</h2>
<div>
<h4>Department</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Name)
</dt>
<dd>
@Html.DisplayFor(model => model.Name)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Budget)
</dt>
<dd>
@Html.DisplayFor(model => model.Budget)
</dd>
<dt>
@Html.DisplayNameFor(model => model.StartDate)
</dt>
<dd>
@Html.DisplayFor(model => model.StartDate)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Administrator)
</dt>
<dd>
@Html.DisplayFor(model => model.Administrator.FullName)
</dd>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model.DepartmentID">Edit</a> |
<a asp-action="Index">Back to List</a>
</div>
Reemplace el código de Views/Departments/Create.cshtml para agregar una opción Select en la lista desplegable.
@model ContosoUniversity.Models.Department
@{
ViewData["Title"] = "Create";
}
<h2>Create</h2>
<h4>Department</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Budget" class="control-label"></label>
<input asp-for="Budget" class="form-control" />
<span asp-validation-for="Budget" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="StartDate" class="control-label"></label>
<input asp-for="StartDate" class="form-control" />
<span asp-validation-for="StartDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="InstructorID" class="control-label"></label>
<select asp-for="InstructorID" class="form-control" asp-items="ViewBag.InstructorID">
<option value="">-- Select Administrator --</option>
</select>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Resumen
Con esto finaliza la introducción para el control de los conflictos de simultaneidad. Para obtener más información
sobre cómo administrar la simultaneidad en EF Core, vea Concurrency conflicts (Conflictos de simultaneidad). El
siguiente tutorial muestra cómo implementar la herencia de tabla por jerarquía para las entidades Instructor y
Student.
A N T E R IO R S IG U IE N T E
ASP.NET Core MVC con EF Core: herencia (9 de 10)
25/06/2018 • 14 minutes to read • Edit Online
Imagine que quiere eliminar el código redundante de las propiedades que comparten las entidades Instructor
y Student . O que quiere escribir un servicio que pueda dar formato a los nombres sin preocuparse de si el
nombre es de un instructor o de un alumno. Puede crear una clase base Person que solo contenga las
propiedades compartidas y después hacer que las clases Instructor y Student hereden de esa clase base,
como se muestra en la siguiente ilustración:
Esta estructura de herencia se puede representar de varias formas en la base de datos. Puede tener una sola
tabla Person que incluya información sobre los alumnos y los instructores. Algunas de las columnas podrían
aplicarse solo a los instructores (HireDate), otras solo a los alumnos (EnrollmentDate) y otras a ambos
(LastName, FirstName). Lo más común sería que tuviera una columna discriminadora para indicar qué tipo
representa cada fila. Por ejemplo, en la columna discriminadora podría aparecer "Instructor" para los
instructores y "Student" para los alumnos.
Este patrón, que consiste en generar una estructura de herencia de la entidad a partir de una tabla de base de
datos única, se denomina herencia de tabla por jerarquía (TPH).
Una alternativa consiste en hacer que la base de datos se parezca más a la estructura de herencia. Por ejemplo,
podría tener solo los campos de nombre en la tabla Person y tener tablas Instructor y Student independientes
con los campos de fecha.
Este patrón, que consiste en crear una tabla de base de datos para cada clase de entidad, se denomina herencia
de tabla por tipo (TPT).
Y todavía otra opción pasa por asignar todos los tipos no abstractos a tablas individuales. Todas las propiedades
de una clase, incluidas las propiedades heredadas, se asignan a columnas de la tabla correspondiente. Este
patrón se denomina herencia de tabla por clase concreta (TPC ). Si implementara la herencia de TPC en las
clases Person, Student e Instructor tal como se ha explicado anteriormente, las tablas Student e Instructor se
verían igual antes que después de implementar la herencia.
Los patrones de herencia de TPC y TPH acostumbran a tener un mejor rendimiento que los patrones de
herencia de TPT, porque estos pueden provocar consultas de combinaciones complejas.
Este tutorial muestra cómo implementar la herencia de TPH. TPH es el único patrón de herencia que Entity
Framework Core admite. Lo que tiene que hacer es crear una clase Person , cambiar las clases Instructor y
Student que deriven de Person , agregar la nueva clase a DbContext y crear una migración.
TIP
Considere la posibilidad de guardar una copia del proyecto antes de realizar los siguientes cambios. De este modo, si tiene
problemas y necesita volver a empezar, le resultará más fácil comenzar desde el proyecto guardado que tener que revertir
los pasos realizados en este tutorial o volver al principio de toda la serie.
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public abstract class Person
{
public int ID { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
[Column("FirstName")]
[Display(Name = "First Name")]
public string FirstMidName { get; set; }
namespace ContosoUniversity.Models
{
public class Instructor : Person
{
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Hire Date")]
public DateTime HireDate { get; set; }
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Student : Person
{
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Enrollment Date")]
public DateTime EnrollmentDate { get; set; }
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
modelBuilder.Entity<CourseAssignment>()
.HasKey(c => new { c.CourseID, c.InstructorID });
}
}
}
Esto es todo lo que Entity Framework necesita para configurar la herencia de tabla por jerarquía. Como verá,
cuando la base de datos esté actualizada, tendrá una tabla Person en lugar de las tablas Student e Instructor.
No ejecute el comando database update todavía. Este comando provocará la pérdida de datos porque colocará
la tabla Instructor y cambiará el nombre de la tabla Student por Person. Deberá proporcionar código
personalizado para conservar los datos existentes.
Abra Migrations/<marca_de_tiempo>_Inheritance.cs y reemplace el método Up por el código siguiente:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Enrollment_Student_StudentID",
table: "Enrollment");
migrationBuilder.DropTable(
name: "Student");
migrationBuilder.CreateIndex(
name: "IX_Enrollment_StudentID",
table: "Enrollment",
column: "StudentID");
migrationBuilder.AddForeignKey(
name: "FK_Enrollment_Person_StudentID",
table: "Enrollment",
column: "StudentID",
principalTable: "Person",
principalColumn: "ID",
onDelete: ReferentialAction.Cascade);
}
(En un sistema de producción haría los cambios correspondientes en el método Down por si alguna vez tuviera
que usarlo para volver a la versión anterior de la base de datos. Para este tutorial, no se usará el método Down ).
NOTE
Al hacer cambios en el esquema, se pueden generar otros errores en una base de datos que contenga los datos
existentes. Si se producen errores de migración que no se pueden resolver, puede cambiar el nombre de la base de datos
en la cadena de conexión o eliminar la base de datos. Con una nueva base de datos, no habrá ningún dato para migrar y
es más probable que el comando de actualización de base de datos se complete sin errores. Para eliminar la base de datos,
use SSOX o ejecute el comando database drop de la CLI.
Haga clic con el botón derecho en la tabla Person y después haga clic en Mostrar datos de tabla para ver la
columna discriminadora.
Resumen
Ha implementado la herencia de tabla por jerarquía en las clases Person , Student y Instructor . Para obtener
más información sobre la herencia en Entity Framework Core, consulte Herencia. En el siguiente tutorial,
aprenderá a controlar una serie de escenarios de Entity Framework relativamente avanzados.
A N T E R IO R S IG U IE N T E
ASP.NET Core MVC con EF Core: Avanzado (10 de
10)
25/06/2018 • 24 minutes to read • Edit Online
if (department == null)
{
return NotFound();
}
return View(department);
}
Para comprobar que el nuevo código funciona correctamente, seleccione la pestaña Departments y, después,
Details para uno de los departamentos.
if (reader.HasRows)
{
while (await reader.ReadAsync())
{
var row = new EnrollmentDateGroup { EnrollmentDate = reader.GetDateTime(0), StudentCount
= reader.GetInt32(1) };
groups.Add(row);
}
}
reader.Dispose();
}
}
finally
{
conn.Close();
}
return View(groups);
}
using System.Data.Common;
Ejecute la aplicación y vaya a la página About. Muestra los mismos datos que anteriormente.
[HttpPost]
public async Task<IActionResult> UpdateCourseCredits(int? multiplier)
{
if (multiplier != null)
{
ViewData["RowsAffected"] =
await _context.Database.ExecuteSqlCommandAsync(
"UPDATE Course SET Credits = Credits * {0}",
parameters: multiplier);
}
return View();
}
Aquí verá algo que podría sorprenderle: la instrucción SQL selecciona hasta 2 filas ( TOP(2) ) de la tabla Person.
El método SingleOrDefaultAsync no se resuelve en 1 fila en el servidor. El motivo es el siguiente:
Si la consulta devolvería varias filas, el método devuelve NULL.
Para determinar si la consulta devolvería varias filas, EF tiene que comprobar si devuelve al menos 2.
Tenga en cuenta que no tiene que usar el modo de depuración y parar en un punto de interrupción para obtener
los resultados del registro en la ventana Salida. Es una manera cómoda de detener el registro en el punto que
quiere ver en la salida. Si no lo hace, el registro continúa y tiene que desplazarse hacia atrás para encontrar los
elementos que le interesan.
_context.ChangeTracker.AutoDetectChangesEnabled = false;
if (searchString != null)
{
page = 1;
}
else
{
searchString = currentFilter;
}
ViewData["CurrentFilter"] = searchString;
if (!String.IsNullOrEmpty(searchString))
{
students = students.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
if (string.IsNullOrEmpty(sortOrder))
{
sortOrder = "LastName";
}
if (descending)
{
students = students.OrderByDescending(e => EF.Property<object>(e, sortOrder));
}
else
{
students = students.OrderBy(e => EF.Property<object>(e, sortOrder));
}
int pageSize = 3;
return View(await PaginatedList<Student>.CreateAsync(students.AsNoTracking(),
page ?? 1, pageSize));
}
Pasos siguientes
Con esto finaliza esta serie de tutoriales sobre cómo usar Entity Framework Core en una aplicación ASP.NET
MVC.
Para obtener más información sobre EF Core, consulte la documentación de Entity Framework Core. También
está disponible un libro: Entity Framework Core in Action (Entity Framework Core en acción, en inglés).
Para obtener información sobre cómo implementar una aplicación web, consulte Hospedaje e implementación.
Para obtener información sobre otros temas relacionados con ASP.NET Core MVC, como la autenticación y
autorización, consulte la documentación de ASP.NET Core.
Agradecimientos
Tom Dykstra y Rick Anderson (Twitter @RickAndMSFT) escribieron este tutorial. Rowan Miller, Diego Vega y
otros miembros del equipo de Entity Framework participaron con revisiones de código y ayudaron a depurar
problemas que surgieron mientras se estaba escribiendo el código para los tutoriales.
Errores comunes
ContosoUniversity.dll usado por otro proceso
Mensaje de error:
Solución:
Detenga el sitio en IIS Express. Vaya a la bandeja del sistema de Windows, busque IIS Express y haga clic con el
botón derecho en su icono; seleccione el sitio de Contoso University y, después, haga clic en Detener sitio.
La migración aplicó la técnica scaffolding sin código en los métodos Up y Down
Causa posible:
Los comandos de la CLI de EF no cierran y guardan automáticamente los archivos de código. Si no ha guardado
los cambios cuando ejecuta el comando migrations add , EF no encontrará los cambios.
Solución:
Ejecute el comando migrations remove , guarde los cambios de código y vuelva a ejecutar el comando
migrations add .
Solución:
Compruebe la cadena de conexión. Si ha eliminado manualmente el archivo de base de datos, cambie el nombre
de la base de datos en la cadena de construcción para volver a empezar con una base de datos nueva.
A N T E R IO R
Tutoriales de ASP.NET Core
25/06/2018 • 2 minutes to read • Edit Online
Están disponibles las siguientes guías detalladas para desarrollar aplicaciones de ASP.NET Core:
Requisitos previos
Visual Studio for Mac
NOTE
In ASP.NET Core 2.1 or later, webapp is an alias of the razor argument. If the dotnet new webapp <OPTIONS> command
loads the dotnet new command help instead of creating a new Razor Pages app, install the .NET Core 2.1 SDK.
Los comandos anteriores usan el CLI de .NET Core para crear y ejecutar un proyecto de páginas de Razor. Abra
http://localhost:5000 en un explorador para ver la aplicación.
La plantilla predeterminada crea los vínculos y las páginas RazorPagesMovie, Inicio, Sobre y Contacto. Según
el tamaño de la ventana del explorador, tendrá que hacer clic en el icono de navegación para mostrar los vínculos.
Pruebe los vínculos. Los vínculos RazorPagesMovie e Inicio dirigen a la página de índice. Los vínculos Acerca
de y Contacto dirigen a las páginas About y Contact respectivamente.
appsettings.json Configuración
Carpeta Páginas
El archivo _Layout.cshtml contiene elementos HTML comunes (scripts y hojas de estilos) y establece el diseño de
la aplicación. Por ejemplo, al hacer clic en RazorPagesMovie, Inicio, Acerca de o Contacto, verá los mismos
elementos. Los elementos comunes incluyen el menú de navegación de la parte superior y el encabezado de la
parte inferior de la ventana. Consulte Diseño para obtener más información.
El archivo _ViewStart.cshtml establece la propiedad Layout de las páginas de Razor que para usar el archivo
_Layout.cshtml. Vea Layout (Diseño) para más información.
El archivo _ViewImports.cshtml contiene directivas de Razor que se importan en cada página de Razor. Consulte
Importing Shared Directives (Importar directivas compartidas) para obtener más información.
El archivo _ValidationScriptsPartial.cshtml proporciona una referencia de los scripts de validación de jQuery. Al
agregar las páginas Create y Edit más adelante en el tutorial, se usará el archivo
_ValidationScriptsPartial.cshtml.
Las páginas About , Contact y Index son páginas básicas que puede usar para iniciar una aplicación. La página
Error se usa para mostrar información de errores.
Abrir el proyecto
Presione CTRL+C para cerrar la aplicación.
En Visual Studio, seleccione Archivo > Abrir y elija el archivo RazorPagesMovie.csproj.
Iniciar la aplicación
En Visual Studio, seleccione Ejecutar > Iniciar sin depurar para iniciar la aplicación. Visual Studio iniciará
Kestrel y un explorador, y se desplazará a http://localhost:5000 .
En el tutorial siguiente, agregaremos un modelo al proyecto.
S IG U IE N T E : A D IC IÓ N D E U N
M ODELO
Agregar un modelo a una aplicación de páginas de
Razor de ASP.NET Core con Visual Studio para Mac
25/06/2018 • 9 minutes to read • Edit Online
using System;
namespace RazorPagesMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
}
namespace RazorPagesMovie.Models
{
public class MovieContext : DbContext
{
public MovieContext(DbContextOptions<MovieContext> options)
: base(options)
{
}
El código anterior crea una propiedad DbSet para el conjunto de entidades. En la terminología de Entity
Framework, un conjunto de entidades suele corresponderse con una tabla de base de datos, mientras que una
entidad lo hace con una fila de la tabla.
Agregar una cadena de conexión de base de datos
Agregue una cadena de conexión al archivo appsettings.json.
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
},
"ConnectionStrings": {
"MovieContext": "Data Source=MvcMovie.db"
}
}
services.AddDbContext<MovieContext>(options =>
options.UseSqlite(Configuration.GetConnectionString("MovieContext")));
services.AddMvc();
}
Haga clic con el botón derecho en una línea ondulada roja, como MovieContext en la línea
services.AddDbContext<MovieContext>(options => . Seleccione Corrección rápida > con
RazorPagesMovie.Models;. Visual Studio agregará la instrucción de uso.
Compile el proyecto para comprobar que no contenga errores.
Paquetes de Entity Framework Core NuGet para migraciones
Las herramientas de EF para la interfaz de nivel de la línea de comandos se proporcionan en
Microsoft.EntityFrameworkCore.Tools.DotNet. Haga clic en el vínculo
Microsoft.EntityFrameworkCore.Tools.DotNet para obtener el número de versión que debe usar. Para instalar este
paquete, agréguelo a la colección DotNetCliToolReference del archivo .csproj. Nota: Tendrá que instalar este
paquete mediante la edición del archivo .csproj; no se puede usar el comando install-package ni la interfaz
gráfica de usuario del administrador de paquetes.
Para editar un archivo .csproj, haga lo siguiente:
Seleccione Archivo > Abrir y, después, seleccione el archivo .csproj.
Seleccione Opciones.
Cambie Abrir con a Editor de código fuente.
Los números de versión que se muestran en el código que hay a continuación eran correctos en el momento de
escribir este artículo.
El comando add package instala las herramientas necesarias para ejecutar el motor de scaffolding.
El comando ef migrations add InitialCreate genera el código para crear el esquema de base de datos inicial. El
esquema se basa en el modelo especificado en DbContext (en el archivo Models/MovieContext.cs). El argumento
InitialCreate se usa para asignar un nombre a las migraciones. Puede usar cualquier nombre, pero se suele
elegir uno que describa la migración. Vea Introduction to migrations (Introducción a las migraciones) para
obtener más información.
El comando ef database update ejecuta el método Up en el archivo Migrations/<time-stamp>_InitialCreate.cs,
con lo que se crea la base de datos.
Aplicar scaffolding al modelo de película
Ejecute lo siguiente desde la línea de comandos (en el directorio del proyecto que contiene los archivos
Program.cs, Startup.cs y .csproj):
Si se produce un error:
Abra un shell de comandos en el directorio del proyecto (el directorio que contiene los archivos Program.cs,
Startup.cs y .csproj).
Si se produce un error:
The process cannot access the file
'RazorPagesMovie/bin/Debug/netcoreapp2.0/RazorPagesMovie.dll'
because it is being used by another process.
PARÁMETRO DESCRIPTION
Probar la aplicación
Ejecute la aplicación y anexe /Movies a la dirección URL en el explorador ( http://localhost:port/movies ).
Pruebe el vínculo Crear.
Pruebe los vínculos Editar, Detalles y Eliminar.
Si recibe un error similar al siguiente, verifique que haya realizado las migraciones y que la base de datos esté
actualizada:
A N T E R IO R : S IG U IE N T E : P Á G IN A S D E R A Z O R C R E A D A S M E D IA N T E
IN T R O D U C C IÓ N S C A F F O L D IN G
Páginas de Razor con scaffolding en ASP.NET Core
25/06/2018 • 12 minutes to read • Edit Online
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Movie) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Razor puede realizar la transición de HTML a C# o a un marcado específico de Razor. Cuando el símbolo @ va
seguido de una palabra clave reservada de Razor, realiza una transición a un marcado específico de Razor; en caso
contrario, realiza la transición a C#.
La directiva de Razor @page convierte el archivo en una acción — de MVC, lo que significa que puede controlar
las solicitudes. @page debe ser la primera directiva de Razor de una página. @page es un ejemplo de la transición
a un marcado específico de Razor. Vea Razor syntax (Sintaxis de Razor) para más información.
Examine la expresión lambda usada en la siguiente aplicación auxiliar HTML:
@Html.DisplayNameFor(model => model.Movie[0].Title))
La aplicación auxiliar HTML DisplayNameFor inspecciona la propiedad Title a la que se hace referencia en la
expresión lambda para determinar el nombre para mostrar. La expresión lambda se inspecciona, no se evalúa. Esto
significa que no hay ninguna infracción de acceso si model , model.Movie o model.Movie[0] son null o están
vacíos. Al evaluar la expresión lambda (por ejemplo, con @Html.DisplayFor(modelItem => item.Title) ), se evalúan
los valores de propiedad del modelo.
La directiva @model
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
La directiva @model especifica el tipo del modelo que se pasa a la página de Razor. En el ejemplo anterior, la línea
@model permite que la clase derivada de PageModel esté disponible en la página de Razor. El modelo se usa en las
aplicaciones auxiliares HTML @Html.DisplayNameFor y @Html.DisplayName de la página.
Propiedades ViewData y Layout
Observe el código siguiente:
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
@{
ViewData["Title"] = "Index";
}
El código resaltado anterior es un ejemplo de Razor con una transición a C#. Los caracteres { y } delimitan un
bloque de código de C#.
La clase base PageModel tiene una propiedad de diccionario ViewData que se puede usar para agregar datos que
quiera pasar a una vista. Puede agregar objetos al diccionario ViewData con un patrón de clave/valor. En el
ejemplo anterior, se agrega la propiedad "Title" al diccionario ViewData . La propiedad "Title" se usa en el archivo
Pages/_Layout.cshtml. En el siguiente marcado se muestran las primeras líneas del archivo Pages/_Layout.cshtml.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - RazorPagesMovie</title>
La línea @*Markup removed for brevity.*@ es un comentario de Razor. A diferencia de los comentarios HTML (
<!-- --> ), los comentarios de Razor no se envían al cliente.
Ejecute la aplicación y pruebe los vínculos del proyecto (Inicio, Acerca de, Contacto, Crear, Editar y Eliminar).
Cada página establece el título, que puede ver en la pestaña del explorador. Al marcar una página, se usa el título
para el marcador. Pages/Index.cshtml y Pages/Movies/Index.cshtml actualmente tienen el mismo título, pero
puede modificarlos para que tengan valores diferentes.
La propiedad Layout se establece en el archivo Pages/_ViewStart.cshtml:
@{
Layout = "_Layout";
}
El marcado anterior establece el archivo de diseño en Pages/_Layout.cshtml para todos los archivos de Razor en la
carpeta Pages. Vea Layout (Diseño) para más información.
Actualizar el diseño
Cambie el elemento <title> del archivo Pages/_Layout.cshtml para usar una cadena más corta.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Movie</title>
El elemento delimitador anterior es una aplicación auxiliar de etiquetas. En este caso, se trata de la aplicación
auxiliar de etiquetas Anchor. El atributo y valor de la aplicación auxiliar de etiquetas asp-page="/Movies/Index"
crea un vínculo a la página de Razor /Movies/Index .
Guarde los cambios y pruebe la aplicación haciendo clic en el vínculo RpMovie. Consulte el archivo
_Layout.cshtml en GitHub.
Modelo de página Crear
Examine el modelo de página Pages/Movies/Create.cshtml.cs:
// Unused usings removed.
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesMovie.Models;
using System.Threading.Tasks;
namespace RazorPagesMovie.Pages.Movies
{
public class CreateModel : PageModel
{
private readonly RazorPagesMovie.Models.MovieContext _context;
[BindProperty]
public Movie Movie { get; set; }
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
}
El método OnGet inicializa cualquier estado necesario para la página. La página Crear no tiene ningún estado para
inicializar. El método Page crea un objeto PageResult que representa la página Create.cshtml.
La propiedad Movie usa el atributo [BindProperty] para participar en el enlace de modelos. Cuando el
formulario de creación publica los valores del formulario, el tiempo de ejecución de ASP.NET Core enlaza los
valores publicados con el modelo Movie .
El método OnPostAsync se ejecuta cuando la página publica los datos del formulario:
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
Si hay algún error de modelo, se vuelve a mostrar el formulario, junto con los datos del formulario publicados. La
mayoría de los errores de modelo se pueden capturar en el cliente antes de que se publique el formulario. Un
ejemplo de un error de modelo consiste en publicar un valor para el campo de fecha que no se puede convertir en
una fecha. Más adelante en el tutorial volveremos a hablar de la validación del lado cliente y de la validación de
modelos.
Si no hay ningún error de modelo, los datos se guardan y el explorador se redirige a la página Índice.
Página de Razor Crear
Examine el archivo de la página de Razor Pages/Movies/Create.cshtml:
@page
@model RazorPagesMovie.Pages.Movies.CreateModel
@{
ViewData["Title"] = "Create";
}
<h2>Create</h2>
<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.ReleaseDate" class="control-label"></label>
<input asp-for="Movie.ReleaseDate" class="form-control" />
<span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Genre" class="control-label"></label>
<input asp-for="Movie.Genre" class="form-control" />
<span asp-validation-for="Movie.Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Price" class="control-label"></label>
<input asp-for="Movie.Price" class="form-control" />
<span asp-validation-for="Movie.Price" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
El elemento <form method="post"> es una aplicación auxiliar de etiquetas de formulario. La aplicación auxiliar de
etiquetas de formulario incluye automáticamente un token antifalsificación.
El motor de scaffolding crea un marcado de Razor para cada campo del modelo (excepto el identificador) similar
al siguiente:
A N T E R IO R : A D IC IÓ N D E U N S IG U IE N T E :
M ODELO S Q L IT E
Trabajar con SQLite y páginas de Razor
25/06/2018 • 2 minutes to read • Edit Online
services.AddDbContext<MovieContext>(options =>
options.UseSqlite(Configuration.GetConnectionString("MovieContext")));
services.AddMvc();
}
SQLite
Según la información del sitio web de SQLite:
SQLite es un motor de base de datos SQL independiente, de alta confiabilidad, insertado, con características
completas y dominio público. SQLite es el motor de base de datos más usado en el mundo.
Existen muchas herramientas de terceros que se pueden descargar para administrar y ver una base de datos de
SQLite. La imagen de abajo es de DB Browser for SQLite. Si tiene una herramienta favorita de SQLite, deje un
comentario sobre lo que le gusta de ella.
Inicializar la base de datos
Cree una nueva clase denominada SeedData en la carpeta Models. Reemplace el código generado con el
siguiente:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;
namespace RazorPagesMovie.Models
{
public static class SeedData
{
public static void Initialize(IServiceProvider serviceProvider)
{
using (var context = new MovieContext(
serviceProvider.GetRequiredService<DbContextOptions<MovieContext>>()))
{
// Look for any movies.
if (context.Movie.Any())
{
return; // DB has been seeded
}
context.Movie.AddRange(
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-2-12"),
Genre = "Romantic Comedy",
Price = 7.99M
},
new Movie
{
Title = "Ghostbusters",
ReleaseDate = DateTime.Parse("1984-3-13"),
Genre = "Comedy",
Price = 8.99M
},
new Movie
{
Title = "Ghostbusters 2",
ReleaseDate = DateTime.Parse("1986-2-23"),
Genre = "Comedy",
Price = 9.99M
},
new Movie
{
Title = "Rio Bravo",
ReleaseDate = DateTime.Parse("1959-4-15"),
Genre = "Western",
Price = 3.99M
}
);
context.SaveChanges();
}
}
}
}
Agregar el inicializador
Agregue el inicializador al método Main en el archivo Program.cs:
namespace RazorPagesMovie
{
public class Program
{
public static void Main(string[] args)
{
var host = BuildWebHost(args);
try
{
var context = services.GetRequiredService<MovieContext>();
// requires using Microsoft.EntityFrameworkCore;
context.Database.Migrate();
// Requires using RazorPagesMovie.Models;
SeedData.Initialize(services);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred seeding the DB.");
}
}
host.Run();
}
Prueba de la aplicación
Elimine todos los registros de la base de datos (para que se ejecute el método de inicialización). Detenga e inicie la
aplicación para inicializar la base de datos.
La aplicación muestra los datos inicializados.
A N T E R IO R : A D IC IÓ N D E U N S IG U IE N T E : A C T U A L IZ A C IÓ N D E
M ODELO P Á G IN A S
Actualización de las páginas generadas
25/06/2018 • 7 minutes to read • Edit Online
using System;
using System.ComponentModel.DataAnnotations;
namespace RazorPagesMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
Los vínculos Edit, Details y Delete son generados por la aplicación auxiliar de etiquetas de delimitador del
archivo Pages/Movies/Index.cshtml.
Las aplicaciones auxiliares de etiquetas permiten que el código de servidor participe en la creación y la
representación de elementos HTML en archivos de Razor. En el código anterior, AnchorTagHelper genera de forma
dinámica el valor del atributo href HTML desde la página de Razor (la ruta es relativa), el elemento asp-page y
el identificador de ruta ( asp-route-id ). Vea Generación de direcciones URL para las páginas para obtener más
información.
Use Ver código fuente en su explorador preferido para examinar el marcado generado. A continuación se
muestra una parte del HTML generado:
<td>
<a href="/Movies/Edit?id=1">Edit</a> |
<a href="/Movies/Details?id=1">Details</a> |
<a href="/Movies/Delete?id=1">Delete</a>
</td>
Los vínculos generados de forma dinámica pasan el identificador de la película con una cadena de consulta (por
ejemplo, http://localhost:5000/Movies/Details?id=2 ).
Actualice las páginas de edición, detalles y eliminación de Razor para usar la plantilla de ruta "{id:int}". Cambie la
directiva de página de cada una de estas páginas de @page a @page "{id:int}" . Ejecute la aplicación y luego vea
el origen. El HTML generado agrega el identificador a la parte de la ruta de acceso de la dirección URL:
<td>
<a href="/Movies/Edit/1">Edit</a> |
<a href="/Movies/Details/1">Details</a> |
<a href="/Movies/Delete/1">Delete</a>
</td>
Una solicitud a la página con la plantilla de ruta "{id:int}" que no incluya el entero devolverá un error HTTP 404
(no encontrado). Por ejemplo, http://localhost:5000/Movies/Details devolverá un error 404. Para que el
identificador sea opcional, anexe ? a la restricción de ruta:
@page "{id:int?}"
_context.Attach(Movie).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!_context.Movie.Any(e => e.ID == Movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
El código anterior solo detecta las excepciones de simultaneidad cuando el primer cliente simultáneo elimina la
película y el segundo cliente simultáneo publica cambios en ella.
Para probar el bloque catch :
Establecer un punto de interrupción en catch (DbUpdateConcurrencyException)
Edite una película.
En otra ventana del explorador, seleccione el vínculo de eliminación de la misma película y luego elimínela.
En la ventana anterior del explorador, publique los cambios en la película.
El código de producción por lo general debería detectar conflictos de simultaneidad cuando dos o más clientes
actualizan un registro de forma simultánea. Vea Administración de conflictos de simultaneidad para más
información.
Revisión de publicaciones y enlaces
Examine el archivo Pages/Movies/Edit.cshtml.cs: [!code-csharp]
Cuando se realiza una solicitud HTTP GET a la página Movies/Edit (por ejemplo,
http://localhost:5000/Movies/Edit/2 ):
Si hay errores en el estado del modelo (por ejemplo, ReleaseDate no se puede convertir en una fecha), el
formulario se vuelve a publicar con los valores enviados.
Si no hay ningún error en el modelo, se guarda la película.
Los métodos HTTP GET de las páginas de índice, creación y eliminación de Razor siguen un patrón similar. El
método HTTP POST OnPostAsync de la página de creación de Razor sigue un patrón similar al del método
OnPostAsync de la página de edición de Razor.
A N T E R IO R : T R A B A J A R C O N A GREGA R
S Q L IT E BÚSQUEDA
Adición de búsqueda a la aplicación páginas de
Razor
25/06/2018 • 6 minutes to read • Edit Online
@{
Layout = "_Layout";
}
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
La primera línea del método OnGetAsync crea una consulta LINQ para seleccionar las películas:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
El código s => s.Title.Contains() es una expresión lambda. Las lambdas se usan en consultas LINQ basadas en
métodos como argumentos para métodos de operador de consulta estándar, como el método Where o Contains
(usado en el código anterior). Las consultas LINQ no se ejecutan cuando se definen ni cuando se modifican
mediante una llamada a un método (como Where , Contains u OrderBy ). En su lugar, se aplaza la ejecución de la
consulta. Esto significa que la evaluación de una expresión se aplaza hasta que su valor realizado se repite o se
llama al método ToListAsync . Para más información, vea Query Execution (Ejecución de consultas).
Nota: El método Contains se ejecuta en la base de datos, no en el código de C#. La distinción entre mayúsculas y
minúsculas en la consulta depende de la base de datos y la intercalación. En SQL Server, Contains se asigna a
SQL LIKE, que distingue entre mayúsculas y minúsculas. En SQLite, con la intercalación predeterminada, se
distingue entre mayúsculas y minúsculas.
Vaya a la página de películas y anexe una cadena de consulta como ?searchString=Ghost a la dirección URL (por
ejemplo, http://localhost:5000/Movies?searchString=Ghost ). Se muestran las películas filtradas.
Si se agrega la siguiente plantilla de ruta a la página de índice, la cadena de búsqueda se puede pasar como un
segmento de dirección URL (por ejemplo, http://localhost:5000/Movies/ghost ).
@page "{searchString?}"
La restricción de ruta anterior permite buscar el título como datos de ruta (un segmento de dirección URL ) en
lugar de como un valor de cadena de consulta. El elemento ? de "{searchString?}" significa que se trata de un
parámetro de ruta opcional.
Sin embargo, no se puede esperar que los usuarios modifiquen la dirección URL para buscar una película. En este
paso, se agrega la interfaz de usuario para filtrar las películas. Si ha agregado la restricción de ruta
"{searchString?}" , quítela.
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<form>
<p>
Title: <input type="text" name="SearchString">
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
@*Markup removed for brevity.*@
La etiqueta HTML <form> usa la aplicación auxiliar de etiquetas de formulario. Cuando se envía el formulario, la
cadena de filtro se envía a la página Pages/Movies/Index. Guarde los cambios y pruebe el filtro.
El elemento SelectList Genres contiene la lista de géneros. Esto permite al usuario seleccionar un género de la
lista.
La propiedad MovieGenre contiene el género concreto que selecciona el usuario (por ejemplo, "Western").
Actualice el método OnGetAsync con el código siguiente:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (!String.IsNullOrEmpty(movieGenre))
{
movies = movies.Where(x => x.Genre == movieGenre);
}
Genres = new SelectList(await genreQuery.Distinct().ToListAsync());
Movie = await movies.ToListAsync();
}
El código siguiente es una consulta LINQ que recupera todos los géneros de la base de datos.
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<form>
<p>
<select asp-for="MovieGenre" asp-items="Model.Genres">
<option value="">All</option>
</select>
<table class="table">
<thead>
A N T E R IO R : A C T U A L IZ A C IÓ N D E S IG U IE N T E : A D IC IÓ N D E U N N U E V O
P Á G IN A S CAM PO
Creación de una aplicación web de páginas de Razor
con ASP.NET Core y Visual Studio Code
21/06/2018 • 2 minutes to read • Edit Online
Requisitos previos
Install the following:
.NET Core SDK 2.0 or later
Visual Studio Code
C# for Visual Studio Code
.NET Core 2.1 SDK or later
Visual Studio Code
C# for Visual Studio Code
NOTE
In ASP.NET Core 2.1 or later, webapp is an alias of the razor argument. If the dotnet new webapp <OPTIONS> command
loads the dotnet new command help instead of creating a new Razor Pages app, install the .NET Core 2.1 SDK.
Los comandos anteriores usan el CLI de .NET Core para crear y ejecutar un proyecto de páginas de Razor. Abra
http://localhost:5000 en un explorador para ver la aplicación.
La plantilla predeterminada crea los vínculos y las páginas RazorPagesMovie, Inicio, Sobre y Contacto. Según
el tamaño de la ventana del explorador, tendrá que hacer clic en el icono de navegación para mostrar los vínculos.
Pruebe los vínculos. Los vínculos RazorPagesMovie e Inicio dirigen a la página de índice. Los vínculos Acerca
de y Contacto dirigen a las páginas About y Contact respectivamente.
appsettings.json Configuración
Carpeta Páginas
El archivo _Layout.cshtml contiene elementos HTML comunes (scripts y hojas de estilos) y establece el diseño de
la aplicación. Por ejemplo, al hacer clic en RazorPagesMovie, Inicio, Acerca de o Contacto, verá los mismos
elementos. Los elementos comunes incluyen el menú de navegación de la parte superior y el encabezado de la
parte inferior de la ventana. Consulte Diseño para obtener más información.
El archivo _ViewStart.cshtml establece la propiedad Layout de las páginas de Razor que para usar el archivo
_Layout.cshtml. Vea Layout (Diseño) para más información.
El archivo _ViewImports.cshtml contiene directivas de Razor que se importan en cada página de Razor. Consulte
Importing Shared Directives (Importar directivas compartidas) para obtener más información.
El archivo _ValidationScriptsPartial.cshtml proporciona una referencia de los scripts de validación de jQuery. Al
agregar las páginas Create y Edit más adelante en el tutorial, se usará el archivo
_ValidationScriptsPartial.cshtml.
Las páginas About , Contact y Index son páginas básicas que puede usar para iniciar una aplicación. La página
Error se usa para mostrar información de errores.
Abrir el proyecto
Presione CTRL+C para cerrar la aplicación.
En Visual Studio Code (VS Code), seleccione Archivo > Abrir carpeta y elija la carpeta RazorPagesMovie.
Seleccione Sí en el mensaje de advertencia "Required assets to build and debug are missing from
'RazorPagesMovie'. Add them?" (Faltan los activos necesarios para compilar y depurar en 'RazorPagesMovie'.
Add them?" (Faltan los activos necesarios para compilar y depurar en 'TodoApi'. ¿Quiere agregarlos?).
Seleccione Restaurar en el mensaje de información "Hay dependencias no resueltas".
Iniciar la aplicación
Presione Ctrl+F5 para iniciar la aplicación sin depurar. Si lo prefiere, puede ir al menú Depurar y seleccionar
Iniciar sin depurar.
En el tutorial siguiente, agregaremos un modelo al proyecto.
S IG U IE N T E : A D IC IÓ N D E U N
M ODELO
Agregar un modelo a una aplicación de páginas de
Razor de ASP.NET Core con Visual Studio Code
25/06/2018 • 7 minutes to read • Edit Online
using System;
namespace RazorPagesMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
}
namespace RazorPagesMovie.Models
{
public class MovieContext : DbContext
{
public MovieContext(DbContextOptions<MovieContext> options)
: base(options)
{
}
El código anterior crea una propiedad DbSet para el conjunto de entidades. En la terminología de Entity
Framework, un conjunto de entidades suele corresponderse con una tabla de base de datos, mientras que una
entidad lo hace con una fila de la tabla.
Agregar una cadena de conexión de base de datos
Agregue una cadena de conexión al archivo appsettings.json.
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
},
"ConnectionStrings": {
"MovieContext": "Data Source=MvcMovie.db"
}
}
services.AddDbContext<MovieContext>(options =>
options.UseSqlite(Configuration.GetConnectionString("MovieContext")));
services.AddMvc();
}
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.3" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.2" />
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.1" />
</ItemGroup>
</Project>
El comando add package instala las herramientas necesarias para ejecutar el motor de scaffolding.
El comando ef migrations add InitialCreate genera el código para crear el esquema de base de datos inicial. El
esquema se basa en el modelo especificado en DbContext (en el archivo Models/MovieContext.cs). El argumento
InitialCreate se usa para asignar un nombre a las migraciones. Puede usar cualquier nombre, pero se suele
elegir uno que describa la migración. Vea Introduction to migrations (Introducción a las migraciones) para
obtener más información.
El comando ef database update ejecuta el método Up en el archivo Migrations/<time-stamp>_InitialCreate.cs,
con lo que se crea la base de datos.
Aplicar scaffolding al modelo de película
Abra una ventana de comandos en el directorio del proyecto (el directorio que contiene los archivos
Program.cs, Startup.cs y .csproj).
Ejecute el siguiente comando:
Nota: ejecute el siguiente comando en Windows. Para MacOS y Linux, vea el siguiente comando
Si se produce un error:
The process cannot access the file
'RazorPagesMovie/bin/Debug/netcoreapp2.0/RazorPagesMovie.dll'
because it is being used by another process.
PARÁMETRO DESCRIPTION
Probar la aplicación
Ejecute la aplicación y anexe /Movies a la dirección URL en el explorador ( http://localhost:port/movies ).
Pruebe el vínculo Crear.
Pruebe los vínculos Editar, Detalles y Eliminar.
Si recibe un error similar al siguiente, verifique que haya realizado las migraciones y que la base de datos esté
actualizada:
A N T E R IO R : S IG U IE N T E : P Á G IN A S D E R A Z O R C R E A D A S M E D IA N T E
IN T R O D U C C IÓ N S C A F F O L D IN G
Páginas de Razor con scaffolding en ASP.NET Core
25/06/2018 • 12 minutes to read • Edit Online
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Movie) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Razor puede realizar la transición de HTML a C# o a un marcado específico de Razor. Cuando el símbolo @ va
seguido de una palabra clave reservada de Razor, realiza una transición a un marcado específico de Razor; en caso
contrario, realiza la transición a C#.
La directiva de Razor @page convierte el archivo en una acción — de MVC, lo que significa que puede controlar
las solicitudes. @page debe ser la primera directiva de Razor de una página. @page es un ejemplo de la transición
a un marcado específico de Razor. Vea Razor syntax (Sintaxis de Razor) para más información.
Examine la expresión lambda usada en la siguiente aplicación auxiliar HTML:
@Html.DisplayNameFor(model => model.Movie[0].Title))
La aplicación auxiliar HTML DisplayNameFor inspecciona la propiedad Title a la que se hace referencia en la
expresión lambda para determinar el nombre para mostrar. La expresión lambda se inspecciona, no se evalúa. Esto
significa que no hay ninguna infracción de acceso si model , model.Movie o model.Movie[0] son null o están
vacíos. Al evaluar la expresión lambda (por ejemplo, con @Html.DisplayFor(modelItem => item.Title) ), se evalúan
los valores de propiedad del modelo.
La directiva @model
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
La directiva @model especifica el tipo del modelo que se pasa a la página de Razor. En el ejemplo anterior, la línea
@model permite que la clase derivada de PageModel esté disponible en la página de Razor. El modelo se usa en las
aplicaciones auxiliares HTML @Html.DisplayNameFor y @Html.DisplayName de la página.
Propiedades ViewData y Layout
Observe el código siguiente:
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
@{
ViewData["Title"] = "Index";
}
El código resaltado anterior es un ejemplo de Razor con una transición a C#. Los caracteres { y } delimitan un
bloque de código de C#.
La clase base PageModel tiene una propiedad de diccionario ViewData que se puede usar para agregar datos que
quiera pasar a una vista. Puede agregar objetos al diccionario ViewData con un patrón de clave/valor. En el
ejemplo anterior, se agrega la propiedad "Title" al diccionario ViewData . La propiedad "Title" se usa en el archivo
Pages/_Layout.cshtml. En el siguiente marcado se muestran las primeras líneas del archivo Pages/_Layout.cshtml.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - RazorPagesMovie</title>
La línea @*Markup removed for brevity.*@ es un comentario de Razor. A diferencia de los comentarios HTML (
<!-- --> ), los comentarios de Razor no se envían al cliente.
Ejecute la aplicación y pruebe los vínculos del proyecto (Inicio, Acerca de, Contacto, Crear, Editar y Eliminar).
Cada página establece el título, que puede ver en la pestaña del explorador. Al marcar una página, se usa el título
para el marcador. Pages/Index.cshtml y Pages/Movies/Index.cshtml actualmente tienen el mismo título, pero
puede modificarlos para que tengan valores diferentes.
La propiedad Layout se establece en el archivo Pages/_ViewStart.cshtml:
@{
Layout = "_Layout";
}
El marcado anterior establece el archivo de diseño en Pages/_Layout.cshtml para todos los archivos de Razor en la
carpeta Pages. Vea Layout (Diseño) para más información.
Actualizar el diseño
Cambie el elemento <title> del archivo Pages/_Layout.cshtml para usar una cadena más corta.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Movie</title>
El elemento delimitador anterior es una aplicación auxiliar de etiquetas. En este caso, se trata de la aplicación
auxiliar de etiquetas Anchor. El atributo y valor de la aplicación auxiliar de etiquetas asp-page="/Movies/Index"
crea un vínculo a la página de Razor /Movies/Index .
Guarde los cambios y pruebe la aplicación haciendo clic en el vínculo RpMovie. Consulte el archivo
_Layout.cshtml en GitHub.
Modelo de página Crear
Examine el modelo de página Pages/Movies/Create.cshtml.cs:
// Unused usings removed.
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesMovie.Models;
using System.Threading.Tasks;
namespace RazorPagesMovie.Pages.Movies
{
public class CreateModel : PageModel
{
private readonly RazorPagesMovie.Models.MovieContext _context;
[BindProperty]
public Movie Movie { get; set; }
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
}
El método OnGet inicializa cualquier estado necesario para la página. La página Crear no tiene ningún estado para
inicializar. El método Page crea un objeto PageResult que representa la página Create.cshtml.
La propiedad Movie usa el atributo [BindProperty] para participar en el enlace de modelos. Cuando el
formulario de creación publica los valores del formulario, el tiempo de ejecución de ASP.NET Core enlaza los
valores publicados con el modelo Movie .
El método OnPostAsync se ejecuta cuando la página publica los datos del formulario:
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
Si hay algún error de modelo, se vuelve a mostrar el formulario, junto con los datos del formulario publicados. La
mayoría de los errores de modelo se pueden capturar en el cliente antes de que se publique el formulario. Un
ejemplo de un error de modelo consiste en publicar un valor para el campo de fecha que no se puede convertir en
una fecha. Más adelante en el tutorial volveremos a hablar de la validación del lado cliente y de la validación de
modelos.
Si no hay ningún error de modelo, los datos se guardan y el explorador se redirige a la página Índice.
Página de Razor Crear
Examine el archivo de la página de Razor Pages/Movies/Create.cshtml:
@page
@model RazorPagesMovie.Pages.Movies.CreateModel
@{
ViewData["Title"] = "Create";
}
<h2>Create</h2>
<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.ReleaseDate" class="control-label"></label>
<input asp-for="Movie.ReleaseDate" class="form-control" />
<span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Genre" class="control-label"></label>
<input asp-for="Movie.Genre" class="form-control" />
<span asp-validation-for="Movie.Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Price" class="control-label"></label>
<input asp-for="Movie.Price" class="form-control" />
<span asp-validation-for="Movie.Price" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
El elemento <form method="post"> es una aplicación auxiliar de etiquetas de formulario. La aplicación auxiliar de
etiquetas de formulario incluye automáticamente un token antifalsificación.
El motor de scaffolding crea un marcado de Razor para cada campo del modelo (excepto el identificador) similar
al siguiente:
A N T E R IO R : A D IC IÓ N D E U N S IG U IE N T E :
M ODELO S Q L IT E
Trabajar con SQLite y páginas de Razor
25/06/2018 • 2 minutes to read • Edit Online
services.AddDbContext<MovieContext>(options =>
options.UseSqlite(Configuration.GetConnectionString("MovieContext")));
services.AddMvc();
}
SQLite
Según la información del sitio web de SQLite:
SQLite es un motor de base de datos SQL independiente, de alta confiabilidad, insertado, con características
completas y dominio público. SQLite es el motor de base de datos más usado en el mundo.
Existen muchas herramientas de terceros que se pueden descargar para administrar y ver una base de datos de
SQLite. La imagen de abajo es de DB Browser for SQLite. Si tiene una herramienta favorita de SQLite, deje un
comentario sobre lo que le gusta de ella.
Inicializar la base de datos
Cree una nueva clase denominada SeedData en la carpeta Models. Reemplace el código generado con el
siguiente:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;
namespace RazorPagesMovie.Models
{
public static class SeedData
{
public static void Initialize(IServiceProvider serviceProvider)
{
using (var context = new MovieContext(
serviceProvider.GetRequiredService<DbContextOptions<MovieContext>>()))
{
// Look for any movies.
if (context.Movie.Any())
{
return; // DB has been seeded
}
context.Movie.AddRange(
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-2-12"),
Genre = "Romantic Comedy",
Price = 7.99M
},
new Movie
{
Title = "Ghostbusters",
ReleaseDate = DateTime.Parse("1984-3-13"),
Genre = "Comedy",
Price = 8.99M
},
new Movie
{
Title = "Ghostbusters 2",
ReleaseDate = DateTime.Parse("1986-2-23"),
Genre = "Comedy",
Price = 9.99M
},
new Movie
{
Title = "Rio Bravo",
ReleaseDate = DateTime.Parse("1959-4-15"),
Genre = "Western",
Price = 3.99M
}
);
context.SaveChanges();
}
}
}
}
Agregar el inicializador
Agregue el inicializador al método Main en el archivo Program.cs:
namespace RazorPagesMovie
{
public class Program
{
public static void Main(string[] args)
{
var host = BuildWebHost(args);
try
{
var context = services.GetRequiredService<MovieContext>();
// requires using Microsoft.EntityFrameworkCore;
context.Database.Migrate();
// Requires using RazorPagesMovie.Models;
SeedData.Initialize(services);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred seeding the DB.");
}
}
host.Run();
}
Prueba de la aplicación
Elimine todos los registros de la base de datos (para que se ejecute el método de inicialización). Detenga e inicie la
aplicación para inicializar la base de datos.
La aplicación muestra los datos inicializados.
A N T E R IO R : A D IC IÓ N D E U N S IG U IE N T E : A C T U A L IZ A C IÓ N D E
M ODELO P Á G IN A S
Actualización de las páginas generadas
25/06/2018 • 7 minutes to read • Edit Online
using System;
using System.ComponentModel.DataAnnotations;
namespace RazorPagesMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
Los vínculos Edit, Details y Delete son generados por la aplicación auxiliar de etiquetas de delimitador del
archivo Pages/Movies/Index.cshtml.
Las aplicaciones auxiliares de etiquetas permiten que el código de servidor participe en la creación y la
representación de elementos HTML en archivos de Razor. En el código anterior, AnchorTagHelper genera de forma
dinámica el valor del atributo href HTML desde la página de Razor (la ruta es relativa), el elemento asp-page y
el identificador de ruta ( asp-route-id ). Vea Generación de direcciones URL para las páginas para obtener más
información.
Use Ver código fuente en su explorador preferido para examinar el marcado generado. A continuación se
muestra una parte del HTML generado:
<td>
<a href="/Movies/Edit?id=1">Edit</a> |
<a href="/Movies/Details?id=1">Details</a> |
<a href="/Movies/Delete?id=1">Delete</a>
</td>
Los vínculos generados de forma dinámica pasan el identificador de la película con una cadena de consulta (por
ejemplo, http://localhost:5000/Movies/Details?id=2 ).
Actualice las páginas de edición, detalles y eliminación de Razor para usar la plantilla de ruta "{id:int}". Cambie la
directiva de página de cada una de estas páginas de @page a @page "{id:int}" . Ejecute la aplicación y luego vea
el origen. El HTML generado agrega el identificador a la parte de la ruta de acceso de la dirección URL:
<td>
<a href="/Movies/Edit/1">Edit</a> |
<a href="/Movies/Details/1">Details</a> |
<a href="/Movies/Delete/1">Delete</a>
</td>
Una solicitud a la página con la plantilla de ruta "{id:int}" que no incluya el entero devolverá un error HTTP 404
(no encontrado). Por ejemplo, http://localhost:5000/Movies/Details devolverá un error 404. Para que el
identificador sea opcional, anexe ? a la restricción de ruta:
@page "{id:int?}"
_context.Attach(Movie).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!_context.Movie.Any(e => e.ID == Movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
El código anterior solo detecta las excepciones de simultaneidad cuando el primer cliente simultáneo elimina la
película y el segundo cliente simultáneo publica cambios en ella.
Para probar el bloque catch :
Establecer un punto de interrupción en catch (DbUpdateConcurrencyException)
Edite una película.
En otra ventana del explorador, seleccione el vínculo de eliminación de la misma película y luego elimínela.
En la ventana anterior del explorador, publique los cambios en la película.
El código de producción por lo general debería detectar conflictos de simultaneidad cuando dos o más clientes
actualizan un registro de forma simultánea. Vea Administración de conflictos de simultaneidad para más
información.
Revisión de publicaciones y enlaces
Examine el archivo Pages/Movies/Edit.cshtml.cs: [!code-csharp]
Cuando se realiza una solicitud HTTP GET a la página Movies/Edit (por ejemplo,
http://localhost:5000/Movies/Edit/2 ):
Si hay errores en el estado del modelo (por ejemplo, ReleaseDate no se puede convertir en una fecha), el
formulario se vuelve a publicar con los valores enviados.
Si no hay ningún error en el modelo, se guarda la película.
Los métodos HTTP GET de las páginas de índice, creación y eliminación de Razor siguen un patrón similar. El
método HTTP POST OnPostAsync de la página de creación de Razor sigue un patrón similar al del método
OnPostAsync de la página de edición de Razor.
A N T E R IO R : T R A B A J A R C O N A GREGA R
S Q L IT E BÚSQUEDA
Adición de búsqueda a la aplicación páginas de
Razor
25/06/2018 • 6 minutes to read • Edit Online
@{
Layout = "_Layout";
}
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
La primera línea del método OnGetAsync crea una consulta LINQ para seleccionar las películas:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
El código s => s.Title.Contains() es una expresión lambda. Las lambdas se usan en consultas LINQ basadas en
métodos como argumentos para métodos de operador de consulta estándar, como el método Where o Contains
(usado en el código anterior). Las consultas LINQ no se ejecutan cuando se definen ni cuando se modifican
mediante una llamada a un método (como Where , Contains u OrderBy ). En su lugar, se aplaza la ejecución de la
consulta. Esto significa que la evaluación de una expresión se aplaza hasta que su valor realizado se repite o se
llama al método ToListAsync . Para más información, vea Query Execution (Ejecución de consultas).
Nota: El método Contains se ejecuta en la base de datos, no en el código de C#. La distinción entre mayúsculas y
minúsculas en la consulta depende de la base de datos y la intercalación. En SQL Server, Contains se asigna a
SQL LIKE, que distingue entre mayúsculas y minúsculas. En SQLite, con la intercalación predeterminada, se
distingue entre mayúsculas y minúsculas.
Vaya a la página de películas y anexe una cadena de consulta como ?searchString=Ghost a la dirección URL (por
ejemplo, http://localhost:5000/Movies?searchString=Ghost ). Se muestran las películas filtradas.
Si se agrega la siguiente plantilla de ruta a la página de índice, la cadena de búsqueda se puede pasar como un
segmento de dirección URL (por ejemplo, http://localhost:5000/Movies/ghost ).
@page "{searchString?}"
La restricción de ruta anterior permite buscar el título como datos de ruta (un segmento de dirección URL ) en
lugar de como un valor de cadena de consulta. El elemento ? de "{searchString?}" significa que se trata de un
parámetro de ruta opcional.
Sin embargo, no se puede esperar que los usuarios modifiquen la dirección URL para buscar una película. En este
paso, se agrega la interfaz de usuario para filtrar las películas. Si ha agregado la restricción de ruta
"{searchString?}" , quítela.
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<form>
<p>
Title: <input type="text" name="SearchString">
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
@*Markup removed for brevity.*@
La etiqueta HTML <form> usa la aplicación auxiliar de etiquetas de formulario. Cuando se envía el formulario, la
cadena de filtro se envía a la página Pages/Movies/Index. Guarde los cambios y pruebe el filtro.
El elemento SelectList Genres contiene la lista de géneros. Esto permite al usuario seleccionar un género de la
lista.
La propiedad MovieGenre contiene el género concreto que selecciona el usuario (por ejemplo, "Western").
Actualice el método OnGetAsync con el código siguiente:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (!String.IsNullOrEmpty(movieGenre))
{
movies = movies.Where(x => x.Genre == movieGenre);
}
Genres = new SelectList(await genreQuery.Distinct().ToListAsync());
Movie = await movies.ToListAsync();
}
El código siguiente es una consulta LINQ que recupera todos los géneros de la base de datos.
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<form>
<p>
<select asp-for="MovieGenre" asp-items="Model.Genres">
<option value="">All</option>
</select>
<table class="table">
<thead>
A N T E R IO R : A C T U A L IZ A C IÓ N D E S IG U IE N T E : A D IC IÓ N D E U N N U E V O
P Á G IN A S CAM PO
Creación de una aplicación web con ASP.NET Core
MVC en macOS con Visual Studio para Mac
21/06/2018 • 2 minutes to read • Edit Online
En esta serie de tutoriales aprenderá los aspectos básicos de la creación de una aplicación web de ASP.NET Core
MVC con Visual Studio para Mac.
En este tutorial se muestra el desarrollo web de ASP.NET Core MVC con controladores y vistas. Las páginas de
Razor son una nueva alternativa en ASP.NET Core 2.0 y posteriores, un modelo de programación basado en
páginas que facilita la compilación de interfaces de usuario web y hace que sean más productivas. Se recomienda
probar el tutorial de las páginas de Razor antes que la versión MVC. El tutorial de las páginas de Razor:
Es el método preferido para el desarrollo de nuevas aplicaciones.
Es más fácil de seguir.
Abarca más características.
Si elige este tutorial en lugar de la versión de páginas de Razor, le agradeceremos que nos explique el motivo en
este problema de GitHub.
1. Introducción
2. Agregar un controlador
3. Agregar una vista
4. Agregar un modelo
5. SQLite
6. Vistas y métodos de controlador
7. Agregar búsqueda
8. Agregar un campo nuevo
9. Agregar validación
10. Examinar los métodos Details y Delete
Introducción a ASP.NET Core MVC y Visual Studio
para Mac
25/06/2018 • 3 minutes to read • Edit Online
Requisitos previos
Visual Studio for Mac
S IG U IE N T E
Agregar un controlador a una aplicación de ASP.NET
Core MVC con Visual Studio para Mac
25/06/2018 • 10 minutes to read • Edit Online
Adición de un controlador
En el Explorador de soluciones, haga clic con el botón derecho en Controladores > Agregar > Nuevo
archivo.
Seleccione ASP.NET Core y Clase de controlador de MVC.
Asigne al controlador el nombre HelloWorldController.
namespace MvcMovie.Controllers
{
public class HelloWorldController : Controller
{
//
// GET: /HelloWorld/
//
// GET: /HelloWorld/Welcome/
Cada método public en un controlador puede ser invocado como un punto de conexión HTTP. En el ejemplo
anterior, ambos métodos devuelven una cadena. Observe los comentarios delante de cada método.
Un extremo HTTP es una dirección URL que se puede usar como destino en la aplicación web, como por ejemplo
http://localhost:1234/HelloWorld . Combina el protocolo usado HTTP , la ubicación de red del servidor web
(incluido el puerto TCP ) localhost:1234 y el URI de destino HelloWorld .
El primer comentario dice que se trata de un método HTTP GET que se invoca mediante la anexión de
"/HelloWorld/" a la dirección URL base. El segundo comentario dice que se trata de un método HTTP GET que se
invoca mediante la anexión de "/HelloWorld/Welcome/" a la dirección URL. Más adelante en el tutorial usaremos
el motor de scaffolding para generar métodos HTTP POST .
Ejecute la aplicación en modo de no depuración y anexione "HelloWorld" a la ruta de acceso en la barra de
direcciones. El método Index devuelve una cadena.
MVC invoca las clases del controlador (y los métodos de acción que contienen) en función de la URL entrante. La
lógica de enrutamiento de URL predeterminada que usa MVC emplea un formato similar al siguiente para
determinar qué código se debe invocar:
/[Controller]/[ActionName]/[Parameters]
Modifique el código para pasar cierta información del parámetro desde la dirección URL al controlador. Por
ejemplo: /HelloWorld/Welcome?name=Rick&numtimes=4 . Cambie el método Welcome para que incluya dos parámetros,
como se muestra en el código siguiente.
// GET: /HelloWorld/Welcome/
// Requires using System.Text.Encodings.Web;
public string Welcome(string name, int numTimes = 1)
{
return HtmlEncoder.Default.Encode($"Hello {name}, NumTimes is: {numTimes}");
}
El código anterior:
Usa la característica de parámetro opcional de C# para indicar que el parámetro numTimes tiene el valor
predeterminado 1 si no se pasa ningún valor para ese parámetro.
Usa HtmlEncoder.Default.Encode para proteger la aplicación de entradas malintencionadas (es decir,
JavaScript).
Usa cadenas interpoladas.
Ejecute la aplicación y navegue a:
http://localhost:xxxx/HelloWorld/Welcome?name=Rick&numtimes=4
(Reemplace xxxx con el número de puerto). Puede probar valores diferentes para name y numtimes en la dirección
URL. El sistema de enlace de modelos de MVC asigna automáticamente los parámetros con nombre de la cadena
de consulta en la barra de dirección a los parámetros del método. Vea Model Binding (Enlace de modelos) para
más información.
En la ilustración anterior, el segmento de dirección URL ( Parameters ) no se usa, y los parámetros name y
numTimes se pasan como cadenas de consulta. El ? (signo de interrogación) en la dirección URL anterior es un
separador y le siguen las cadenas de consulta. El carácter & separa las cadenas de consulta.
Reemplace el método Welcome con el código siguiente:
Esta vez el tercer segmento de dirección URL coincide con el parámetro de ruta id . El método Welcome contiene
un parámetro id que coincide con la plantilla de dirección URL en el método MapRoute . El ? del final (en id? )
indica que el parámetro id es opcional.
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
En estos ejemplos, el controlador ha realizado la parte "VC" de MVC, es decir, el trabajo de vista y de controlador.
El controlador devuelve HTML directamente. Por lo general, no es aconsejable que los controles devuelvan HTML
directamente, porque resulta muy complicado de programar y mantener. En su lugar, se suele usar un archivo de
plantilla de vista de Razor independiente para ayudar a generar la respuesta HTML. Haremos esto en el siguiente
tutorial.
A N T E R IO R S IG U IE N T E
Adición de una vista en una aplicación de ASP.NET
Core MVC
25/06/2018 • 14 minutes to read • Edit Online
El código anterior devuelve un objeto View . Usa una plantilla de vista para generar una respuesta HTML al
explorador. Los métodos de controlador (también conocidos como métodos de acción), como el método Index
anterior, suelen devolver IActionResult o una clase derivada de ActionResult , en lugar de un tipo como una
cadena.
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
Si la ventana del explorador es pequeña (por ejemplo en un dispositivo móvil), es conveniente que alterne (pulse)
el botón de navegación de arranque en la esquina superior derecha para ver los vínculos Home (Inicio), About
(Acerca de) y Contact (Contacto).
Cambiar vistas y páginas de diseño
Pulse los vínculos de menú: MvcMovie (Película de MVC ), Home (Inicio), About (Acerca de). Cada página
muestra el mismo diseño de menú. El diseño de menú se implementa en el archivo Views/Shared/_Layout.cshtml.
Abra el archivo Views/Shared/_Layout.cshtml.
Las plantillas de diseño permiten especificar el diseño del contenedor HTML del sitio en un solo lugar y, después,
aplicarlo en varias páginas del sitio. Busque la línea @RenderBody() . RenderBody es un marcador de posición
donde se mostrarán todas las páginas específicas de vista que cree, encapsuladas en la página de diseño. Por
ejemplo, si selecciona el vínculo About (Acerca de), la vista Views/Home/About.cshtml se representa dentro
del método RenderBody .
[!code-html]
[!code-html]
WARNING
Aún no hemos implementado el controlador Movies , por lo que si hace clic en ese vínculo, obtendrá un error 404 (no
encontrado).
Guarde los cambios y pulse en el vínculo About (Acerca de). Observe cómo el título de la pestaña del explorador
muestra ahora About - Movie App (Acerca de - Aplicación de película) en lugar de About - Mvc Movie (Acerca
de - Aplicación de MVC ):
Pulse el vínculo Contacto y observe que el texto del título y el delimitador también muestran Movie App.
Hemos realizado el cambio una vez en la plantilla de diseño y hemos conseguido que todas las páginas del sitio
reflejen el nuevo texto de vínculo y el nuevo título.
Examine el archivo Views/_ViewStart.cshtml:
@{
Layout = "_Layout";
}
Haremos que sean algo diferentes para que pueda ver qué parte del código cambia cada área de la aplicación.
@{
ViewData["Title"] = "Movie List";
}
En el código anterior, ViewData["Title"] = "Movie List"; establece la propiedad Title del diccionario ViewData
en "Movie List" (Lista de películas). La propiedad Title se usa en el elemento HTML <title> en la página de
diseño:
Guarde el cambio y navegue a http://localhost:xxxx/HelloWorld . Tenga en cuenta que el título del explorador, el
encabezado principal y los encabezados secundarios han cambiado. (Si no ve los cambios en el explorador, es
posible que esté viendo contenido almacenado en caché. Presione Ctrl+F5 en el explorador para forzar que se
cargue la respuesta del servidor). El título del explorador se crea con ViewData["Title"] , que se definió en la
plantilla de vista Index.cshtml y el texto "- Movie App" (-Aplicación de película) que se agregó en el archivo de
diseño.
Observe también cómo el contenido de la plantilla de vista Index.cshtml se fusionó con la plantilla de vista
Views/Shared/_Layout.cshtml y se envió una única respuesta HTML al explorador. Con las plantillas de diseño es
realmente fácil hacer cambios para que se apliquen en todas las páginas de la aplicación. Para saber más, vea
Layout (Diseño).
Nuestra pequeña cantidad de "datos", en este caso, el mensaje "Hello from our View Template!" (Hola desde
nuestra plantilla de vista), están codificados de forma rígida. La aplicación de MVC tiene una "V" (vista) y ha
obtenido una "C" (controlador), pero todavía no tiene una "M" (modelo).
using Microsoft.AspNetCore.Mvc;
using System.Text.Encodings.Web;
namespace MvcMovie.Controllers
{
public class HelloWorldController : Controller
{
public IActionResult Index()
{
return View();
}
return View();
}
}
}
@{
ViewData["Title"] = "Welcome";
}
<h2>Welcome</h2>
<ul>
@for (int i = 0; i < (int)ViewData["NumTimes"]; i++)
{
<li>@ViewData["Message"]</li>
}
</ul>
Los datos se toman de la dirección URL y se pasan al controlador mediante el enlazador de modelos MVC. El
controlador empaqueta los datos en un diccionario ViewData y pasa ese objeto a la vista. Después, la vista
representa los datos como HTML en el explorador.
En el ejemplo anterior, usamos el diccionario ViewData para pasar datos del controlador a una vista. Más adelante
en el tutorial usaremos un modelo de vista para pasar datos de un controlador a una vista. El enfoque del modelo
de vista que consiste en pasar datos suele ser más preferible que el enfoque de diccionario ViewData . Para saber
más, vea ViewModel vs ViewData vs ViewBag vs TempData vs Session in MVC (ViewModel, ViewData, ViewBag,
TempData y Session en MVC ).
Bueno, todo esto era un tipo de "M" para el modelo, pero no el tipo de base de datos. Vamos a aprovechar lo que
hemos aprendido para crear una base de datos de películas.
A N T E R IO R S IG U IE N T E
Agregar un modelo a una aplicación ASP.NET Core
MVC con Visual Studio para Mac
25/06/2018 • 7 minutes to read • Edit Online
using System;
namespace MvcMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
}
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0"
/>
</ItemGroup>
</Project>
Guarde el archivo.
Cree un archivo Modelos/MvcMovieContext.cs y agregue la siguiente clase MvcMovieContext : [!code-csharp]
Abra el archivo Startup.cs y agregue dos instrucciones using: [!code-csharp]
Agregue el contexto de base de datos al archivo Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
services.AddDbContext<MvcMovieContext>(options =>
options.UseSqlite("Data Source=MvcMovie.db"));
Esto indica a Entity Framework las clases de modelo que se incluyen en el modelo de datos. Se va a definir
un conjunto de entidades de objetos Movie, que se representarán en la base de datos como una tabla de
películas.
Compile el proyecto para comprobar que no hay ningún error.
dotnet restore
dotnet aspnet-codegenerator controller -name MoviesController -m Movie -dc MvcMovieContext --
relativeFolderPath Controllers --useDefaultLayout --referenceScriptLibraries
El comando dotnet ef migrations add InitialCreate genera el código para crear el esquema de base de datos
inicial. El esquema se basa en el modelo especificado en DbContext (en el archivo Models/MvcMovieContext.cs). El
argumento Initial se usa para asignar nombre a las migraciones. Puede usar cualquier nombre, pero se suele
elegir uno que describa la migración. Vea Introduction to migrations (Introducción a las migraciones) para
obtener más información.
El comando dotnet ef database update ejecuta el método Up en el archivo Migrations/<time-
stamp>_InitialCreate.cs, con lo que se crea la base de datos.
Prueba de la aplicación
Ejecute la aplicación y pulse en el vínculo Mvc Movie (Película Mvc).
Pulse Create New (Crear nueva) y cree una película.
Es posible no que pueda escribir comas ni puntos decimales en el campo Price . Para admitir la validación
de jQuery para configuraciones regionales distintas del inglés que usan una coma (",") en lugar de un
punto decimal y formatos de fecha distintos de Estados Unidos, debe seguir unos pasos globalizar la
aplicación. Consulte https://github.com/aspnet/Docs/issues/4076 y recursos adicionales para obtener más
información. Por ahora, tan solo debe escribir números enteros como 10.
En algunas configuraciones regionales debe especificar el formato de fecha. Vea el código que aparece
resaltado a continuación.
using System;
using System.ComponentModel.DataAnnotations;
namespace MvcMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
}
Cree un par de entradas más de película. Compruebe que todos los vínculos Editar, Detalles y Eliminar son
funcionales.
Ahora dispone de una base de datos y de páginas para mostrar, editar, actualizar y eliminar datos. En el siguiente
tutorial trabajaremos con la base de datos.
Recursos adicionales
Aplicaciones auxiliares de etiquetas
Globalización y localización
A N T E R IO R : A G R E G A R U N A S IG U IE N T E : T R A B A J A R C O N
V IS T A SQL
Trabajar con SQLite en un proyecto de ASP.NET Core
MVC
25/06/2018 • 2 minutes to read • Edit Online
services.AddDbContext<MvcMovieContext>(options =>
options.UseSqlite("Data Source=MvcMovie.db"));
SQLite
Según la información del sitio web de SQLite:
SQLite es un motor de base de datos SQL independiente, de alta confiabilidad, insertado, con características
completas y dominio público. SQLite es el motor de base de datos más usado en el mundo.
Existen muchas herramientas de terceros que se pueden descargar para administrar y ver una base de datos de
SQLite. La imagen de abajo es de DB Browser for SQLite. Si tiene una herramienta favorita de SQLite, deje un
comentario sobre lo que le gusta de ella.
Inicializar la base de datos
Cree una nueva clase denominada SeedData en la carpeta Models. Reemplace el código generado con el
siguiente:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;
namespace MvcMovie.Models
{
public static class SeedData
{
public static void Initialize(IServiceProvider serviceProvider)
{
using (var context = new MvcMovieContext(
serviceProvider.GetRequiredService<DbContextOptions<MvcMovieContext>>()))
{
// Look for any movies.
if (context.Movie.Any())
{
return; // DB has been seeded
}
context.Movie.AddRange(
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-1-11"),
Genre = "Romantic Comedy",
Price = 7.99M
},
new Movie
{
Title = "Ghostbusters ",
ReleaseDate = DateTime.Parse("1984-3-13"),
Genre = "Comedy",
Price = 8.99M
},
new Movie
{
Title = "Ghostbusters 2",
ReleaseDate = DateTime.Parse("1986-2-23"),
Genre = "Comedy",
Price = 9.99M
},
new Movie
{
Title = "Rio Bravo",
ReleaseDate = DateTime.Parse("1959-4-15"),
Genre = "Western",
Price = 3.99M
}
);
context.SaveChanges();
}
}
}
}
Agregar el inicializador
Agregue el inicializador al método Main en el archivo Program.cs:
[!code-csharp]
[!code-csharp]
Prueba de la aplicación
Elimine todos los registros de la base de datos (para que se ejecute el método de inicialización). Detenga e inicie la
aplicación para inicializar la base de datos.
La aplicación muestra los datos inicializados.
A N T E R IO R : A G R E G A R U N S IG U IE N T E : V IS T A S Y M É T O D O S D E
M ODELO C ON TROL A D OR
Vistas y métodos de controlador en una aplicación
de ASP.NET Core MVC
25/06/2018 • 15 minutes to read • Edit Online
Abra el archivo Models/Movie.cs y agregue las líneas resaltadas que se muestran a continuación:
using System;
using System.ComponentModel.DataAnnotations;
namespace MvcMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
Los vínculos Edit (Editar), Details (Detalles) y Delete (Eliminar) se generan mediante la aplicación auxiliar de
etiquetas de delimitador de MVC Core en el archivo Views/Movies/Index.cshtml.
Las aplicaciones auxiliares de etiquetas permiten que el código de servidor participe en la creación y la
representación de elementos HTML en archivos de Razor. En el código anterior, AnchorTagHelper genera
dinámicamente el valor del atributo HTML href a partir del identificador de ruta y el método de acción del
controlador. Use Ver código fuente en su explorador preferido o use las herramientas de desarrollo para
examinar el marcado generado. A continuación se muestra una parte del HTML generado:
<td>
<a href="/Movies/Edit/4"> Edit </a> |
<a href="/Movies/Details/4"> Details </a> |
<a href="/Movies/Delete/4"> Delete </a>
</td>
ASP.NET Core traduce http://localhost:1234/Movies/Edit/4 en una solicitud al método de acción Edit del
controlador Movies con el parámetro Id de 4. (Los métodos de controlador también se denominan métodos de
acción).
Las aplicaciones auxiliares de etiquetas son una de las nuevas características más populares de ASP.NET Core.
Para más información, vea Recursos adicionales.
Abra el controlador Movies y examine los dos métodos de acción Edit . En el código siguiente se muestra el
método HTTP GET Edit , que captura la película y rellena el formulario de edición generado por el archivo de Razor
Edit.cshtml.
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
En el código siguiente se muestra el método HTTP POST Edit , que procesa los valores de película publicados:
// POST: Movies/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
if (id != movie.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction("Index");
}
return View(movie);
}
El atributo [Bind] es una manera de proteger contra el exceso de publicación. Solo debe incluir propiedades en el
atributo [Bind] que quiera cambiar. Para más información, vea Protección del controlador frente al exceso de
publicación. ViewModels ofrece un enfoque alternativo para evitar el exceso de publicaciones.
Observe que el segundo método de acción Edit va precedido del atributo [HttpPost] .
// POST: Movies/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
if (id != movie.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction("Index");
}
return View(movie);
}
El atributo HttpPost especifica que este método Edit se puede invocar solamente para solicitudes POST . Podría
aplicar el atributo [HttpGet] al primer método de edición, pero no es necesario hacerlo porque [HttpGet] es el
valor predeterminado.
El atributo ValidateAntiForgeryToken se usa para impedir la falsificación de una solicitud y se empareja con un
token antifalsificación generado en el archivo de vista de edición (Views/Movies/Edit.cshtml). El archivo de vista de
edición genera el token antifalsificación con la aplicación auxiliar de etiquetas de formulario.
<form asp-action="Edit">
La aplicación auxiliar de etiquetas de formulario genera un token antifalsificación oculto que debe coincidir con el
token antifalsificación generado por [ValidateAntiForgeryToken] en el método Edit del controlador Movies. Para
más información, vea Prevención de ataques de falsificación de solicitudes.
El método HttpGet Edit toma el parámetro ID de la película, busca la película con el método
SingleOrDefaultAsync de Entity Framework y devuelve la película seleccionada a la vista de edición. Si no se
encuentra una película, se devuelve NotFound (HTTP 404).
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
Cuando el sistema de scaffolding creó la vista de edición, examinó la clase Movie y creó código para representar
los elementos <label> y <input> para cada propiedad de la clase. En el ejemplo siguiente se muestra la vista de
edición que generó el sistema de scaffolding de Visual Studio:
@model MvcMovie.Models.Movie
@{
ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<form asp-action="Edit">
<div class="form-horizontal">
<h4>Movie</h4>
<hr />
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="ID" />
<div class="form-group">
<label asp-for="Title" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="ReleaseDate" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="ReleaseDate" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="Genre" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Genre" class="form-control" />
<span asp-validation-for="Genre" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="Price" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
</form>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Observe cómo la plantilla de vista tiene una instrucción @model MvcMovie.Models.Movie en la parte superior del
archivo. @model MvcMovie.Models.Movie especifica que la vista espera que el modelo de la plantilla de vista sea del
tipo Movie .
El código con scaffolding usa varios métodos de aplicación auxiliar de etiquetas para simplificar el marcado
HTML. La aplicación auxiliar de etiquetas muestra el nombre del campo: "Title" (Título), "ReleaseDate" (Fecha de
lanzamiento), "Genre" (Género) o "Price" (Precio). La aplicación auxiliar de etiquetas de entrada representa un
elemento HTML <input> . La aplicación auxiliar de etiquetas de validación muestra cualquier mensaje de
validación asociado a esa propiedad.
Ejecute la aplicación y navegue a la URL /Movies . Haga clic en un vínculo Edit (Editar). En el explorador, vea el
código fuente de la página. El código HTML generado para el elemento <form> se muestra abajo.
Los elementos <input> se muestran en un elemento HTML <form> cuyo atributo action se establece para
publicar en la dirección URL /Movies/Edit/id . Los datos del formulario se publicarán en el servidor cuando se
haga clic en el botón Save . La última línea antes del cierre del elemento </form> muestra el token XSRF oculto
generado por la aplicación auxiliar de etiquetas de formulario.
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction("Index");
}
return View(movie);
}
El atributo [ValidateAntiForgeryToken] valida el token XSRF oculto generado por el generador de tokens
antifalsificación en la herramienta auxiliar de etiquetas de formulario.
El sistema de enlace de modelos toma los valores de formulario publicados y crea un objeto Movie que se pasa
como el parámetro movie . El método ModelState.IsValid comprueba que los datos presentados en el formulario
pueden usarse para modificar (editar o actualizar) un objeto Movie . Si los datos son válidos, se guardan. Los
datos de película actualizados (o modificados) se guardan en la base de datos mediante una llamada al método
SaveChangesAsync del contexto de base de datos. Después de guardar los datos, el código redirige al usuario al
método de acción Index de la clase MoviesController , que muestra la colección de películas, incluidos los
cambios que se acaban de hacer.
Antes de que el formulario se envíe al servidor, la validación del lado cliente comprueba cualquier regla de
validación en los campos. Si hay errores de validación, se muestra un mensaje de error y no se publica el
formulario. Si JavaScript está deshabilitado, no dispondrá de la validación del lado cliente, sino que el servidor
detectará los valores publicados que no son válidos y los valores de formulario se volverán a mostrar con
mensajes de error. Más adelante en el tutorial se examina la validación de modelos con más detalle. La aplicación
auxiliar de etiquetas de validación en la plantilla de vista Views/Movies/Edit.cshtml se encarga de mostrar los
mensajes de error correspondientes.
Todos los métodos HttpGet del controlador de películas siguen un patrón similar. Obtienen un objeto de película
(o una lista de objetos, en el caso de Index ) y pasan el objeto (modelo) a la vista. El método Create pasa un
objeto de película vacío a la vista Create . Todos los métodos que crean, editan, eliminan o modifican los datos lo
hacen en la sobrecarga [HttpPost] del método. La modificación de datos en un método HTTP GET supone un
riesgo de seguridad. La modificación de datos en un método HTTP GET también infringe procedimientos
recomendados de HTTP y el patrón de arquitectura REST, que especifica que las solicitudes GET no deben
cambiar el estado de la aplicación. En otras palabras, realizar una operación GET debería ser una operación segura
sin efectos secundarios, que no modifica los datos persistentes.
Recursos adicionales
Globalización y localización
Introducción a las aplicaciones auxiliares de etiquetas
Creación de aplicaciones auxiliares de etiquetas
Prevención de ataques de falsificación de solicitudes
Protección del controlador frente al exceso de publicación
ViewModels
Aplicación auxiliar de etiquetas de formulario
Aplicación auxiliar de etiquetas de entrada
Aplicación auxiliar de etiquetas de elementos de etiqueta
Aplicación auxiliar de etiquetas de selección
Aplicación auxiliar de etiquetas de validación
A N T E R IO R : T R A B A J A R C O N S IG U IE N T E : A G R E G A R
S Q L IT E BÚSQUEDA
Adición de una búsqueda en una aplicación de
ASP.NET Core MVC
25/06/2018 • 12 minutes to read • Edit Online
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
La primera línea del método de acción Index crea una consulta LINQ para seleccionar las películas:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
El código s => s.Title.Contains() anterior es una expresión Lambda. Las lambdas se usan en consultas LINQ
basadas en métodos como argumentos para métodos de operador de consulta estándar, tales como el método
Where o Contains (usado en el código anterior). Las consultas LINQ no se ejecutan cuando se definen ni cuando
se modifican mediante una llamada a un método, como Where , Contains u OrderBy . En su lugar, se aplaza la
ejecución de la consulta. Esto significa que la evaluación de una expresión se aplaza hasta que su valor realizado se
repita realmente o se llame al método ToListAsync . Para más información sobre la ejecución de consultas en
diferido, vea Ejecución de la consulta.
Nota: El método Contains se ejecuta en la base de datos, no en el código de c# que se muestra arriba. La
distinción entre mayúsculas y minúsculas en la consulta depende de la base de datos y la intercalación. En SQL
Server, Contains se asigna a SQL LIKE, que distingue entre mayúsculas y minúsculas. En SQLite, con la
intercalación predeterminada, se distingue entre mayúsculas y minúsculas.
Navegue a /Movies/Index . Anexe una cadena de consulta como ?searchString=Ghost a la dirección URL. Se
muestran las películas filtradas.
Si se cambia la firma del método Index para que tenga un parámetro con el nombre id , el parámetro id
coincidirá con el marcador {id} opcional para el conjunto de rutas predeterminado en Startup.cs.
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
Nota: SQLlite distingue mayúsculas de minúsculas, por lo que tendrá que buscar "Ghost" y no "ghost".
El método Index anterior:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (!String.IsNullOrEmpty(id))
{
movies = movies.Where(s => s.Title.Contains(id));
}
Ahora puede pasar el título de la búsqueda como datos de ruta (un segmento de dirección URL ) en lugar de como
un valor de cadena de consulta.
Sin embargo, no se puede esperar que los usuarios modifiquen la dirección URL cada vez que quieran buscar una
película. Por tanto, ahora deberá agregar elementos de la interfaz de usuario con los que podrán filtrar las
películas. Si cambió la firma del método Index para probar cómo pasar el parámetro ID enlazado a una ruta,
vuelva a cambiarlo para que tome un parámetro denominado searchString :
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
La etiqueta HTML <form> usa la aplicación auxiliar de etiquetas de formulario, por lo que cuando se envía el
formulario, la cadena de filtro se registra en la acción Index del controlador de películas. Guarde los cambios y
después pruebe el filtro.
No hay ninguna sobrecarga [HttpPost] del método Index como cabría esperar. No es necesario, porque el
método no cambia el estado de la aplicación, simplemente filtra los datos.
Después, puede agregar el método [HttpPost] Index siguiente.
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
El parámetro notUsed se usa para crear una sobrecarga para el método Index . Hablaremos sobre esto más
adelante en el tutorial.
Si agrega este método, el invocador de acción coincidiría con el método [HttpPost] Index , mientras que el
método [HttpPost] Index se ejecutaría tal como se muestra en la imagen de abajo.
Sin embargo, aunque agregue esta versión de [HttpPost] al método Index , hay una limitación en cómo se ha
implementado todo esto. Supongamos que quiere marcar una búsqueda en particular o que quiere enviar un
vínculo a sus amigos donde puedan hacer clic para ver la misma lista filtrada de películas. Tenga en cuenta que la
dirección URL de la solicitud HTTP POST es la misma que la dirección URL de la solicitud GET
(localhost:xxxxx/Movies/Index): no hay información de búsqueda en la URL. La información de la cadena de
búsqueda se envía al servidor como un valor de campo de formulario. Puede comprobarlo con las herramientas
de desarrollo del explorador o con la excelente herramienta Fiddler. En la imagen de abajo se muestran las
herramientas de desarrollo del explorador Chrome:
Puede ver el parámetro de búsqueda y el token XSRF en el cuerpo de la solicitud. Tenga en cuenta, como se
mencionó en el tutorial anterior, que la aplicación auxiliar de etiquetas de formulario genera un token XSRF
antifalsificación. Como no se van a modificar datos, no es necesario validar el token con el método del controlador.
El parámetro de búsqueda se encuentra en el cuerpo de solicitud y no en la dirección URL. Por eso no se puede
capturar dicha información para marcarla o compartirla con otros usuarios. Para solucionar este problema,
especificaremos que la solicitud sea HTTP GET .
Cambie la etiqueta <form> en la vista de Razor Views\movie\Index.cshtml para especificar method="get" :
<form asp-controller="Movies" asp-action="Index" method="get">
Ahora, cuando se envía una búsqueda, la URL contiene la cadena de consulta de búsqueda. La búsqueda también
será dirigida al método de acción HttpGet Index , aunque tenga un método HttpPost Index .
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace MvcMovie.Models
{
public class MovieGenreViewModel
{
public List<Movie> movies;
public SelectList genres;
public string movieGenre { get; set; }
}
}
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (!String.IsNullOrEmpty(movieGenre))
{
movies = movies.Where(x => x.Genre == movieGenre);
}
return View(movieGenreVM);
}
El código siguiente es una consulta LINQ que recupera todos los géneros de la base de datos.
La SelectList de géneros se crea mediante la proyección de los distintos géneros (no queremos que nuestra lista
de selección tenga géneros duplicados).
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.movies[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.movies[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.movies[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.movies[0].Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.movies)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.ID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Examine la expresión lambda usada en la siguiente aplicación auxiliar HTML:
@Html.DisplayNameFor(model => model.movies[0].Title)
En el código anterior, la aplicación auxiliar HTML DisplayNameFor inspecciona la propiedad Title a la que se
hace referencia en la expresión lambda para determinar el nombre para mostrar. Puesto que la expresión lambda
se inspecciona en lugar de evaluarse, no recibirá una infracción de acceso cuando model , model.movies o
model.movies[0] sean null o estén vacíos. Cuando se evalúa la expresión lambda (por ejemplo,
@Html.DisplayFor(modelItem => item.Title) ), se evalúan los valores de propiedad del modelo.
A N T E R IO R : V IS T A S Y M É T O D O S D E S IG U IE N T E : A G R E G A R U N
C ON TROL A D OR CAM PO
Adición de un nuevo campo
25/06/2018 • 5 minutes to read • Edit Online
[Bind("ID,Title,ReleaseDate,Genre,Price,Rating")]
También necesita actualizar las plantillas de vista para mostrar, crear y editar la nueva propiedad Rating en la
vista del explorador.
Edite el archivo /Views/Movies/Index.cshtml y agregue un campo Rating :
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.movies[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.movies[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.movies[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.movies[0].Price)
</th>
<th>
@Html.DisplayNameFor(model => model.movies[0].Rating)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.movies)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.Rating)
</td>
<td>
Este error se muestra porque la clase del modelo Movie actualizada es diferente del esquema de la tabla Movie de
la base de datos existente. (No hay ninguna columna Rating en la tabla de la base de datos).
Este error se puede resolver de varias maneras:
1. Desconecte la base de datos y haga que Entity Framework vuelva a crear automáticamente la base de datos
según el nuevo esquema de clase de modelo. Con este enfoque se pierden los datos que tenga en la base
de datos, así que no puede hacer esto con una base de datos de producción. A menudo, una forma
productiva de desarrollar una aplicación consiste en usar un inicializador para propagar una base de datos
con datos de prueba.
2. Modifique manualmente el esquema de la base de datos existente para que coincida con las clases de
modelo. La ventaja de este enfoque es que se conservan los datos. Puede realizar este cambio de forma
manual o mediante la creación de un script de cambio de base de datos.
3. Use Migraciones de Code First para actualizar el esquema de la base de datos.
Para este tutorial, se desconectará la base de datos y se volverá a crear cuando cambie el esquema. Para
desconectar la base de datos, ejecute este comando desde un terminal:
dotnet ef database drop
Actualice la clase SeedData para que proporcione un valor para la nueva columna. A continuación se muestra un
cambio de ejemplo, aunque es conveniente realizarlo con cada new Movie .
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-1-11"),
Genre = "Romantic Comedy",
Rating = "R",
Price = 7.99M
},
A N T E R IO R : A G R E G A R S IG U IE N T E : A G R E G A R
BÚSQUEDA V A L ID A C IÓ N
Adición de validación
25/06/2018 • 17 minutes to read • Edit Online
Actualice la clase Movie para aprovechar los atributos de validación integrados Required , StringLength ,
RegularExpression y Range .
[!code-csharp]
[!code-csharp]
Los atributos de validación especifican el comportamiento que quiere aplicar en las propiedades del modelo al
que se aplican. Los atributos Required y MinimumLength indican que una propiedad debe tener un valor, pero nada
evita que un usuario escriba espacios en blanco para satisfacer esta validación. El atributo RegularExpression se
usa para limitar los caracteres que se pueden escribir. En el código anterior, Genre y Rating solamente pueden
usar letras (no se permiten espacios en blanco, números ni caracteres especiales). El atributo Range restringe un
valor a un intervalo determinado. El atributo StringLength permite establecer la longitud máxima de una
propiedad de cadena y, opcionalmente, su longitud mínima. Los tipos de valor (como decimal , int , float ,
DateTime ) son intrínsecamente necesarios y no necesitan el atributo [Required] .
Cuando ASP.NET aplica automáticamente reglas de validación, logramos que la aplicación sea más sólida.
También nos permite asegurarnos de que todo se valida y que no nos dejamos ningún dato incorrecto en la base
de datos accidentalmente.
Observe cómo el formulario presenta automáticamente un mensaje de error de validación adecuado en cada
campo que contiene un valor no válido. Los errores se aplican en el lado cliente (con JavaScript y jQuery) y en el
lado servidor (cuando un usuario tiene JavaScript deshabilitado).
Una ventaja importante es que no fue necesario cambiar ni una sola línea de código en la clase MoviesController
o en la vista Create.cshtml para habilitar esta interfaz de usuario de validación. El controlador y las vistas que creó
en pasos anteriores de este tutorial seleccionaron automáticamente las reglas de validación que especificó
mediante atributos de validación en las propiedades de la clase del modelo Movie . Pruebe la aplicación mediante
el método de acción Edit y se aplicará la misma validación.
Los datos del formulario no se enviarán al servidor hasta que dejen de producirse errores de validación de cliente.
Puede comprobarlo colocando un punto de interrupción en el método HTTP Post mediante la herramienta Fiddler
o las herramientas de desarrollo F12.
// GET: Movies/Create
public IActionResult Create()
{
return View();
}
// POST: Movies/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
[Bind("ID,Title,ReleaseDate,Genre,Price, Rating")] Movie movie)
{
if (ModelState.IsValid)
{
_context.Add(movie);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(movie);
}
El primer método de acción Create (HTTP GET) muestra el formulario de creación inicial. La segunda versión (
[HttpPost] ) controla el envío de formulario. El segundo método Create (la versión [HttpPost] ) llama a
ModelState.IsValid para comprobar si la película tiene errores de validación. Al llamar a este método se evalúan
todos los atributos de validación que se hayan aplicado al objeto. Si el objeto tiene errores de validación, el
método Create vuelve a mostrar el formulario. Si no hay ningún error, el método guarda la nueva película en la
base de datos. En nuestro ejemplo de película, el formulario no se publica en el servidor si se detectan errores de
validación del lado cliente; cuando hay errores de validación en el lado cliente, no se llama nunca al segundo
método Create . Si deshabilita JavaScript en el explorador, se deshabilita también la validación del cliente y puede
probar si el método Create HTTP POST ModelState.IsValid detecta errores de validación.
Puede establecer un punto de interrupción en el método [HttpPost] Create y comprobar si nunca se llama al
método. La validación del lado cliente no enviará los datos del formulario si se detectan errores de validación. Si
deshabilita JavaScript en el explorador y después envía el formulario con errores, se alcanzará el punto de
interrupción. Puede seguir obteniendo validación completa sin JavaScript.
En la siguiente imagen se muestra cómo deshabilitar JavaScript en el explorador Firefox.
En la siguiente imagen se muestra cómo deshabilitar JavaScript en el explorador Chrome.
Después de deshabilitar JavaScript, publique los datos no válidos y siga los pasos del depurador.
Abajo se muestra una parte de la plantilla de vista Create.cshtml a la que se aplicó scaffolding en un paso anterior
de este tutorial. Los métodos de acción que se muestran arriba la usan para mostrar el formulario inicial y para
volver a mostrarlo en caso de error.
<form asp-action="Create">
<div class="form-horizontal">
<h4>Movie</h4>
<hr />
La aplicación auxiliar de etiquetas de entrada usa los atributos DataAnnotations y genera los atributos HTML
necesarios para la validación de jQuery en el lado cliente. La aplicación auxiliar de etiquetas de validación muestra
errores de validación. Para más información, vea Introduction to model validation in ASP.NET Core MVC
(Introducción a la validación de modelos en ASP.NET Core MVC ).
Lo realmente bueno de este enfoque es que ni el controlador ni la plantilla de vista Create saben que las reglas
de validación actuales se están aplicando ni conocen los mensajes de error específicos que se muestran. Las reglas
de validación y las cadenas de error solo se especifican en la clase Movie . Estas mismas reglas de validación se
aplican automáticamente a la vista Edit y a cualquier otra vista de plantillas creada que edite el modelo.
Cuando necesite cambiar la lógica de validación, puede hacerlo exactamente en un solo lugar mediante la adición
de atributos de validación al modelo (en este ejemplo, la clase Movie ). No tendrá que preocuparse de que
diferentes partes de la aplicación sean incoherentes con el modo en que se aplican las reglas: toda la lógica de
validación se definirá en un solo lugar y se usará en todas partes. Esto mantiene el código muy limpio y hace que
sea fácil de mantener y evolucionar. También significa que respeta totalmente el principio DRY.
[Range(1, 100)]
[DataType(DataType.Currency)]
public decimal Price { get; set; }
Los atributos DataType solo proporcionan sugerencias para que el motor de vista aplique formato a los datos (y
ofrece atributos o elementos como <a> para las direcciones URL y <a href="mailto:EmailAddress.com"> para el
correo electrónico). Use el atributo RegularExpression para validar el formato de los datos. El atributo DataType
no es un atributo de validación, sino que se usa para especificar un tipo de datos más específico que el tipo
intrínseco de la base de datos. En este caso solo queremos realizar un seguimiento de la fecha, no la hora. La
enumeración DataType proporciona muchos tipos de datos, como Date (Fecha), Time (Hora), PhoneNumber
(Número de teléfono), Currency (Moneda), EmailAddress (Dirección de correo electrónico), etc. El atributo
DataType también puede permitir que la aplicación proporcione automáticamente características específicas del
tipo. Por ejemplo, se puede crear un vínculo mailto: para DataType.EmailAddress y se puede proporcionar un
selector de datos para DataType.Date en exploradores compatibles con HTML5. Los atributos DataType emiten
atributos HTML 5 data- (se pronuncia "datos dash") que los exploradores HTML 5 pueden comprender. Los
atributos DataType no proporcionan ninguna validación.
DataType.Date no especifica el formato de la fecha que se muestra. De manera predeterminada, el campo de
datos se muestra según los formatos predeterminados basados en el elemento CultureInfo del servidor.
El atributo DisplayFormat se usa para especificar el formato de fecha de forma explícita:
El valor ApplyFormatInEditMode especifica que el formato se debe aplicar también cuando el valor se muestra en
un cuadro de texto para su edición. En algunos campos este comportamiento puede no ser conveniente. Por poner
un ejemplo, es probable que con valores de moneda no se quiera que el símbolo de la divisa se incluya en el
cuadro de texto editable.
El atributo DisplayFormat puede usarse por sí solo, pero normalmente se recomienda usar el atributo DataType .
El atributo DataType transmite la semántica de los datos en contraposición a cómo se representa en una pantalla
y ofrece las siguientes ventajas que no proporciona DisplayFormat:
El explorador puede habilitar características de HTML5 (por ejemplo, mostrar un control de calendario, el
símbolo de moneda adecuado según la configuración regional, vínculos de correo electrónico, etc.).
De manera predeterminada, el explorador representa los datos con el formato correcto según la
configuración regional.
El atributo DataType puede habilitar MVC para que elija la plantilla de campo adecuada para representar
los datos ( DisplayFormat , si se usa por sí solo, usa la plantilla de cadena).
NOTE
La validación de jQuery no funciona con el atributo Range ni DateTime . Por ejemplo, el código siguiente siempre muestra
un error de validación del lado cliente, incluso cuando la fecha está en el intervalo especificado:
[Range(typeof(DateTime), "1/1/1966", "1/1/2020")]
Debe deshabilitar la validación de fechas de jQuery para usar el atributo Range con DateTime . Por lo general no
se recomienda compilar fechas fijas en los modelos, así que desaconseja usar el atributo Range y DateTime .
El código siguiente muestra la combinación de atributos en una línea:
[!code-csharp]
[!code-csharp]
En la siguiente parte de la serie de tutoriales, revisaremos la aplicación y realizaremos algunas mejoras a los
métodos Details y Delete generados automáticamente.
Recursos adicionales
Trabajar con formularios
Globalización y localización
Introducción a las aplicaciones auxiliares de etiquetas
Creación de aplicaciones auxiliares de etiquetas
A N T E R IO R : A G R E G A R U N S IG U IE N T E : E X A M IN A R L O S M É T O D O S D E T A IL S Y
CAM PO D E L E TE
Examinar los métodos Details y Delete de una
aplicación ASP.NET Core
25/06/2018 • 5 minutes to read • Edit Online
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
// GET: Movies/Delete/5
public async Task<IActionResult> Delete(int? id)
{
// POST: Movies/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
Common Language Runtime (CLR ) requiere métodos sobrecargados para disponer de una firma de parámetro
única (mismo nombre de método, pero lista de parámetros diferente). En cambio, aquí necesita dos métodos
Delete (uno para GET y otro para POST ) que tienen la misma firma de parámetro (ambos deben aceptar un
número entero como parámetro).
Hay dos enfoques para este problema. Uno consiste en proporcionar nombres diferentes a los métodos, que es lo
que hizo el mecanismo de scaffolding en el ejemplo anterior. Pero esto implica un pequeño problema: ASP.NET
asigna segmentos de una dirección URL a los métodos de acción por nombre y, si cambia el nombre de un método,
normalmente el enrutamiento no podría encontrar ese método. La solución es la que ve en el ejemplo, que consiste
en agregar el atributo ActionName("Delete") al método DeleteConfirmed . Ese atributo efectúa la asignación para el
sistema de enrutamiento para que una dirección URL que incluya /Delete/ para una solicitud POST busque el
método DeleteConfirmed .
Otra solución alternativa común para los métodos que tienen nombres y firmas idénticos consiste en cambiar la
firma del método POST artificialmente para incluir un parámetro adicional (sin usar). Es lo que hicimos en una
publicación anterior, cuando agregamos el parámetro notUsed . Podría hacer lo mismo aquí para el método
[HttpPost] Delete :
// POST: Movies/Delete/6
[ValidateAntiForgeryToken]
public async Task<IActionResult> Delete(int id, bool notUsed)
Publicar en Azure
Vea Publicar una aplicación web de ASP.NET Core en Azure App Service con Visual Studio para obtener
instrucciones sobre cómo publicar esta aplicación en Azure con Visual Studio. También se puede publicar la
aplicación desde la línea de comandos.
A N T E R IO R
Creación de una aplicación de ASP.NET Core MVC
con Visual Studio Code
21/06/2018 • 2 minutes to read • Edit Online
En esta serie de tutoriales aprenderá los aspectos básicos de la creación de una aplicación web de ASP.NET Core
MVC con Visual Studio Code.
En este tutorial se muestra el desarrollo web de ASP.NET Core MVC con controladores y vistas. Las páginas de
Razor son una nueva alternativa en ASP.NET Core 2.0 y posteriores, un modelo de programación basado en
páginas que facilita la compilación de interfaces de usuario web y hace que sean más productivas. Se recomienda
probar el tutorial de las páginas de Razor antes que la versión MVC. El tutorial de las páginas de Razor:
Es el método preferido para el desarrollo de nuevas aplicaciones.
Es más fácil de seguir.
Abarca más características.
Si elige este tutorial en lugar de la versión de páginas de Razor, le agradeceremos que nos explique el motivo en
este problema de GitHub.
1. Introducción
2. Agregar un controlador
3. Agregar una vista
4. Agregar un modelo
5. Trabajar con SQLite
6. Vistas y métodos de controlador
7. Agregar búsqueda
8. Agregar un campo nuevo
9. Agregar validación
10. Examinar los métodos Details y Delete
Introducción a ASP.NET Core MVC en macOS, Linux
o Windows
25/06/2018 • 4 minutes to read • Edit Online
Requisitos previos
Install the following:
.NET Core SDK 2.0 or later
Visual Studio Code
C# for Visual Studio Code
.NET Core 2.1 SDK or later
Visual Studio Code
C# for Visual Studio Code
mkdir MvcMovie
cd MvcMovie
dotnet new mvc
Abra la carpeta MvcMovie en Visual Studio Code (VS Code) y seleccione el archivo Startup.cs.
Seleccione Sí en el mensaje de advertencia "Required assets to build and debug are missing from
'MvcMovie'. Add them?" (Faltan los activos necesarios para compilar y depurar en 'MvcMovie'. ¿Desea
agregarlos?).
Seleccione Restaurar en el mensaje de información "There are unresolved dependencies" (Hay
dependencias no resueltas).
En la siguiente sección de este tutorial obtendrá información sobre MVC y empezará a escribir código.
using Microsoft.AspNetCore.Mvc;
using System.Text.Encodings.Web;
namespace MvcMovie.Controllers
{
public class HelloWorldController : Controller
{
//
// GET: /HelloWorld/
//
// GET: /HelloWorld/Welcome/
Cada método public en un controlador puede ser invocado como un punto de conexión HTTP. En el ejemplo
anterior, ambos métodos devuelven una cadena. Observe los comentarios delante de cada método.
Un extremo HTTP es una dirección URL que se puede usar como destino en la aplicación web, como por ejemplo
http://localhost:1234/HelloWorld . Combina el protocolo usado HTTP , la ubicación de red del servidor web
(incluido el puerto TCP ) localhost:1234 y el URI de destino HelloWorld .
El primer comentario dice que se trata de un método HTTP GET que se invoca mediante la anexión de
"/HelloWorld/" a la dirección URL base. El segundo comentario dice que se trata de un método HTTP GET que se
invoca mediante la anexión de "/HelloWorld/Welcome/" a la dirección URL. Más adelante en el tutorial usaremos
el motor de scaffolding para generar métodos HTTP POST .
Ejecute la aplicación en modo de no depuración y anexione "HelloWorld" a la ruta de acceso en la barra de
direcciones. El método Index devuelve una cadena.
MVC invoca las clases del controlador (y los métodos de acción que contienen) en función de la URL entrante. La
lógica de enrutamiento de URL predeterminada que usa MVC emplea un formato similar al siguiente para
determinar qué código se debe invocar:
/[Controller]/[ActionName]/[Parameters]
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
// GET: /HelloWorld/Welcome/
// Requires using System.Text.Encodings.Web;
public string Welcome(string name, int numTimes = 1)
{
return HtmlEncoder.Default.Encode($"Hello {name}, NumTimes is: {numTimes}");
}
El código anterior:
Usa la característica de parámetro opcional de C# para indicar que el parámetro numTimes tiene el valor
predeterminado 1 si no se pasa ningún valor para ese parámetro.
Usa HtmlEncoder.Default.Encode para proteger la aplicación de entradas malintencionadas (es decir,
JavaScript).
Usa cadenas interpoladas.
Ejecute la aplicación y navegue a:
http://localhost:xxxx/HelloWorld/Welcome?name=Rick&numtimes=4
(Reemplace xxxx con el número de puerto). Puede probar valores diferentes para name y numtimes en la dirección
URL. El sistema de enlace de modelos de MVC asigna automáticamente los parámetros con nombre de la cadena
de consulta en la barra de dirección a los parámetros del método. Vea Model Binding (Enlace de modelos) para
más información.
En la ilustración anterior, el segmento de dirección URL ( Parameters ) no se usa, y los parámetros name y
numTimes se pasan como cadenas de consulta. El ? (signo de interrogación) en la dirección URL anterior es un
separador y le siguen las cadenas de consulta. El carácter & separa las cadenas de consulta.
Reemplace el método Welcome con el código siguiente:
Esta vez el tercer segmento de dirección URL coincide con el parámetro de ruta id . El método Welcome contiene
un parámetro id que coincide con la plantilla de dirección URL en el método MapRoute . El ? del final (en id? )
indica que el parámetro id es opcional.
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
En estos ejemplos, el controlador ha realizado la parte "VC" de MVC, es decir, el trabajo de vista y de controlador.
El controlador devuelve HTML directamente. Por lo general, no es aconsejable que los controles devuelvan HTML
directamente, porque resulta muy complicado de programar y mantener. En su lugar, se suele usar un archivo de
plantilla de vista de Razor independiente para ayudar a generar la respuesta HTML. Haremos esto en el siguiente
tutorial.
A N T E R IO R : A G R E G A R U N S IG U IE N T E : A G R E G A R U N A
C ON TROL A D OR V IS T A
Adición de una vista en una aplicación de ASP.NET
Core MVC
25/06/2018 • 14 minutes to read • Edit Online
El código anterior devuelve un objeto View . Usa una plantilla de vista para generar una respuesta HTML al
explorador. Los métodos de controlador (también conocidos como métodos de acción), como el método Index
anterior, suelen devolver IActionResult o una clase derivada de ActionResult , en lugar de un tipo como una
cadena.
Agregue una vista Index para el HelloWorldController .
Agregue una nueva carpeta denominada Views/HelloWorld.
Agregue un nuevo archivo a la carpeta Views/HelloWorld y asígnele el nombre Index.cshtml.
Reemplace el contenido del archivo de vista de Razor Views/HelloWorld/Index.cshtml con lo siguiente:
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
[!code-html]
[!code-html]
WARNING
Aún no hemos implementado el controlador Movies , por lo que si hace clic en ese vínculo, obtendrá un error 404 (no
encontrado).
Guarde los cambios y pulse en el vínculo About (Acerca de). Observe cómo el título de la pestaña del explorador
muestra ahora About - Movie App (Acerca de - Aplicación de película) en lugar de About - Mvc Movie (Acerca
de - Aplicación de MVC ):
Pulse el vínculo Contacto y observe que el texto del título y el delimitador también muestran Movie App.
Hemos realizado el cambio una vez en la plantilla de diseño y hemos conseguido que todas las páginas del sitio
reflejen el nuevo texto de vínculo y el nuevo título.
Examine el archivo Views/_ViewStart.cshtml:
@{
Layout = "_Layout";
}
@{
ViewData["Title"] = "Movie List";
}
En el código anterior, ViewData["Title"] = "Movie List"; establece la propiedad Title del diccionario ViewData
en "Movie List" (Lista de películas). La propiedad Title se usa en el elemento HTML <title> en la página de
diseño:
Guarde el cambio y navegue a http://localhost:xxxx/HelloWorld . Tenga en cuenta que el título del explorador, el
encabezado principal y los encabezados secundarios han cambiado. (Si no ve los cambios en el explorador, es
posible que esté viendo contenido almacenado en caché. Presione Ctrl+F5 en el explorador para forzar que se
cargue la respuesta del servidor). El título del explorador se crea con ViewData["Title"] , que se definió en la
plantilla de vista Index.cshtml y el texto "- Movie App" (-Aplicación de película) que se agregó en el archivo de
diseño.
Observe también cómo el contenido de la plantilla de vista Index.cshtml se fusionó con la plantilla de vista
Views/Shared/_Layout.cshtml y se envió una única respuesta HTML al explorador. Con las plantillas de diseño es
realmente fácil hacer cambios para que se apliquen en todas las páginas de la aplicación. Para saber más, vea
Layout (Diseño).
Nuestra pequeña cantidad de "datos", en este caso, el mensaje "Hello from our View Template!" (Hola desde
nuestra plantilla de vista), están codificados de forma rígida. La aplicación de MVC tiene una "V" (vista) y ha
obtenido una "C" (controlador), pero todavía no tiene una "M" (modelo).
using Microsoft.AspNetCore.Mvc;
using System.Text.Encodings.Web;
namespace MvcMovie.Controllers
{
public class HelloWorldController : Controller
{
public IActionResult Index()
{
return View();
}
return View();
}
}
}
<h2>Welcome</h2>
<ul>
@for (int i = 0; i < (int)ViewData["NumTimes"]; i++)
{
<li>@ViewData["Message"]</li>
}
</ul>
Los datos se toman de la dirección URL y se pasan al controlador mediante el enlazador de modelos MVC. El
controlador empaqueta los datos en un diccionario ViewData y pasa ese objeto a la vista. Después, la vista
representa los datos como HTML en el explorador.
En el ejemplo anterior, usamos el diccionario ViewData para pasar datos del controlador a una vista. Más adelante
en el tutorial usaremos un modelo de vista para pasar datos de un controlador a una vista. El enfoque del modelo
de vista que consiste en pasar datos suele ser más preferible que el enfoque de diccionario ViewData . Para saber
más, vea ViewModel vs ViewData vs ViewBag vs TempData vs Session in MVC (ViewModel, ViewData, ViewBag,
TempData y Session en MVC ).
Bueno, todo esto era un tipo de "M" para el modelo, pero no el tipo de base de datos. Vamos a aprovechar lo que
hemos aprendido para crear una base de datos de películas.
A N T E R IO R : A G R E G A R U N S IG U IE N T E : A G R E G A R U N
C ON TROL A D OR M ODELO
Agregar un modelo a una aplicación de ASP.NET
Core MVC
25/06/2018 • 6 minutes to read • Edit Online
using System;
namespace MvcMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
}
using Microsoft.EntityFrameworkCore;
namespace MvcMovie.Models
{
public class MvcMovieContext : DbContext
{
public MvcMovieContext (DbContextOptions<MvcMovieContext> options)
: base(options)
{
}
using Microsoft.EntityFrameworkCore;
using MvcMovie.Models;
namespace MvcMovie
{
public class Startup
{
services.AddDbContext<MvcMovieContext>(options =>
options.UseSqlite("Data Source=MvcMovie.db"));
Esto indica a Entity Framework las clases de modelo que se incluyen en el modelo de datos. Se va a definir
un conjunto de entidades de objetos Movie, que se representarán en la base de datos como una tabla de
películas.
Compile el proyecto para comprobar que no hay ningún error.
dotnet restore
dotnet aspnet-codegenerator controller -name MoviesController -m Movie -dc MvcMovieContext --
relativeFolderPath Controllers --useDefaultLayout --referenceScriptLibraries
El comando dotnet ef migrations add InitialCreate genera el código para crear el esquema de base de datos
inicial. El esquema se basa en el modelo especificado en DbContext (en el archivo Models/MvcMovieContext.cs). El
argumento Initial se usa para asignar nombre a las migraciones. Puede usar cualquier nombre, pero se suele
elegir uno que describa la migración. Vea Introduction to migrations (Introducción a las migraciones) para
obtener más información.
El comando dotnet ef database update ejecuta el método Up en el archivo Migrations/<time-
stamp>_InitialCreate.cs, con lo que se crea la base de datos.
Prueba de la aplicación
Ejecute la aplicación y pulse en el vínculo Mvc Movie (Película Mvc).
Pulse Create New (Crear nueva) y cree una película.
Es posible no que pueda escribir comas ni puntos decimales en el campo Price . Para admitir la validación
de jQuery para configuraciones regionales distintas del inglés que usan una coma (",") en lugar de un
punto decimal y formatos de fecha distintos de Estados Unidos, debe seguir unos pasos globalizar la
aplicación. Consulte https://github.com/aspnet/Docs/issues/4076 y recursos adicionales para obtener más
información. Por ahora, tan solo debe escribir números enteros como 10.
En algunas configuraciones regionales debe especificar el formato de fecha. Vea el código que aparece
resaltado a continuación.
using System;
using System.ComponentModel.DataAnnotations;
namespace MvcMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
}
Hablaremos sobre DataAnnotations más adelante en el tutorial.
Al pulsar en Crear el formulario se envía al servidor, donde se guarda la información de la película en una base de
datos. La aplicación redirige a la URL /Movies, donde se muestra la información de la película que acaba de crear.
Cree un par de entradas más de película. Compruebe que todos los vínculos Editar, Detalles y Eliminar son
funcionales.
Ahora dispone de una base de datos y de páginas para mostrar, editar, actualizar y eliminar datos. En el siguiente
tutorial trabajaremos con la base de datos.
Recursos adicionales
Aplicaciones auxiliares de etiquetas
Globalización y localización
A N T E R IO R : A G R E G A R U N A S IG U IE N T E : T R A B A J A R C O N
V IS T A S Q L IT E
Trabajar con SQLite en un proyecto de ASP.NET
Core MVC
25/06/2018 • 2 minutes to read • Edit Online
services.AddDbContext<MvcMovieContext>(options =>
options.UseSqlite("Data Source=MvcMovie.db"));
SQLite
Según la información del sitio web de SQLite:
SQLite es un motor de base de datos SQL independiente, de alta confiabilidad, insertado, con características
completas y dominio público. SQLite es el motor de base de datos más usado en el mundo.
Existen muchas herramientas de terceros que se pueden descargar para administrar y ver una base de datos de
SQLite. La imagen de abajo es de DB Browser for SQLite. Si tiene una herramienta favorita de SQLite, deje un
comentario sobre lo que le gusta de ella.
Inicializar la base de datos
Cree una nueva clase denominada SeedData en la carpeta Models. Reemplace el código generado con el
siguiente:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;
namespace MvcMovie.Models
{
public static class SeedData
{
public static void Initialize(IServiceProvider serviceProvider)
{
using (var context = new MvcMovieContext(
serviceProvider.GetRequiredService<DbContextOptions<MvcMovieContext>>()))
{
// Look for any movies.
if (context.Movie.Any())
{
return; // DB has been seeded
}
context.Movie.AddRange(
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-1-11"),
Genre = "Romantic Comedy",
Price = 7.99M
},
new Movie
{
Title = "Ghostbusters ",
ReleaseDate = DateTime.Parse("1984-3-13"),
Genre = "Comedy",
Price = 8.99M
},
new Movie
{
Title = "Ghostbusters 2",
ReleaseDate = DateTime.Parse("1986-2-23"),
Genre = "Comedy",
Price = 9.99M
},
new Movie
{
Title = "Rio Bravo",
ReleaseDate = DateTime.Parse("1959-4-15"),
Genre = "Western",
Price = 3.99M
}
);
context.SaveChanges();
}
}
}
}
Agregar el inicializador
Agregue el inicializador al método Main en el archivo Program.cs:
[!code-csharp]
[!code-csharp]
Prueba de la aplicación
Elimine todos los registros de la base de datos (para que se ejecute el método de inicialización). Detenga e inicie la
aplicación para inicializar la base de datos.
La aplicación muestra los datos inicializados.
A N T E R IO R : A G R E G A R U N S IG U IE N T E : V IS T A S Y M É T O D O S D E
M ODELO C ON TROL A D OR
Vistas y métodos de controlador en ASP.NET Core
25/06/2018 • 15 minutes to read • Edit Online
Abra el archivo Models/Movie.cs y agregue las líneas resaltadas que se muestran a continuación:
using System;
using System.ComponentModel.DataAnnotations;
namespace MvcMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
Los vínculos Edit (Editar), Details (Detalles) y Delete (Eliminar) se generan mediante la aplicación auxiliar de
etiquetas de delimitador de MVC Core en el archivo Views/Movies/Index.cshtml.
Las aplicaciones auxiliares de etiquetas permiten que el código de servidor participe en la creación y la
representación de elementos HTML en archivos de Razor. En el código anterior, AnchorTagHelper genera
dinámicamente el valor del atributo HTML href a partir del identificador de ruta y el método de acción del
controlador. Use Ver código fuente en su explorador preferido o use las herramientas de desarrollo para
examinar el marcado generado. A continuación se muestra una parte del HTML generado:
<td>
<a href="/Movies/Edit/4"> Edit </a> |
<a href="/Movies/Details/4"> Details </a> |
<a href="/Movies/Delete/4"> Delete </a>
</td>
ASP.NET Core traduce http://localhost:1234/Movies/Edit/4 en una solicitud al método de acción Edit del
controlador Movies con el parámetro Id de 4. (Los métodos de controlador también se denominan métodos de
acción).
Las aplicaciones auxiliares de etiquetas son una de las nuevas características más populares de ASP.NET Core.
Para más información, vea Recursos adicionales.
Abra el controlador Movies y examine los dos métodos de acción Edit . En el código siguiente se muestra el
método HTTP GET Edit , que captura la película y rellena el formulario de edición generado por el archivo de Razor
Edit.cshtml.
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
En el código siguiente se muestra el método HTTP POST Edit , que procesa los valores de película publicados:
// POST: Movies/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
if (id != movie.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction("Index");
}
return View(movie);
}
El atributo [Bind] es una manera de proteger contra el exceso de publicación. Solo debe incluir propiedades en el
atributo [Bind] que quiera cambiar. Para más información, vea Protección del controlador frente al exceso de
publicación. ViewModels ofrece un enfoque alternativo para evitar el exceso de publicaciones.
Observe que el segundo método de acción Edit va precedido del atributo [HttpPost] .
// POST: Movies/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
if (id != movie.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction("Index");
}
return View(movie);
}
El atributo HttpPost especifica que este método Edit se puede invocar solamente para solicitudes POST . Podría
aplicar el atributo [HttpGet] al primer método de edición, pero no es necesario hacerlo porque [HttpGet] es el
valor predeterminado.
El atributo ValidateAntiForgeryToken se usa para impedir la falsificación de una solicitud y se empareja con un
token antifalsificación generado en el archivo de vista de edición (Views/Movies/Edit.cshtml). El archivo de vista de
edición genera el token antifalsificación con la aplicación auxiliar de etiquetas de formulario.
<form asp-action="Edit">
La aplicación auxiliar de etiquetas de formulario genera un token antifalsificación oculto que debe coincidir con el
token antifalsificación generado por [ValidateAntiForgeryToken] en el método Edit del controlador Movies. Para
más información, vea Prevención de ataques de falsificación de solicitudes.
El método HttpGet Edit toma el parámetro ID de la película, busca la película con el método
SingleOrDefaultAsync de Entity Framework y devuelve la película seleccionada a la vista de edición. Si no se
encuentra una película, se devuelve NotFound (HTTP 404).
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
Cuando el sistema de scaffolding creó la vista de edición, examinó la clase Movie y creó código para representar
los elementos <label> y <input> para cada propiedad de la clase. En el ejemplo siguiente se muestra la vista de
edición que generó el sistema de scaffolding de Visual Studio:
@model MvcMovie.Models.Movie
@{
ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<form asp-action="Edit">
<div class="form-horizontal">
<h4>Movie</h4>
<hr />
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="ID" />
<div class="form-group">
<label asp-for="Title" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="ReleaseDate" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="ReleaseDate" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="Genre" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Genre" class="form-control" />
<span asp-validation-for="Genre" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="Price" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
</form>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Observe cómo la plantilla de vista tiene una instrucción @model MvcMovie.Models.Movie en la parte superior del
archivo. @model MvcMovie.Models.Movie especifica que la vista espera que el modelo de la plantilla de vista sea del
tipo Movie .
El código con scaffolding usa varios métodos de aplicación auxiliar de etiquetas para simplificar el marcado
HTML. La aplicación auxiliar de etiquetas muestra el nombre del campo: "Title" (Título), "ReleaseDate" (Fecha de
lanzamiento), "Genre" (Género) o "Price" (Precio). La aplicación auxiliar de etiquetas de entrada representa un
elemento HTML <input> . La aplicación auxiliar de etiquetas de validación muestra cualquier mensaje de
validación asociado a esa propiedad.
Ejecute la aplicación y navegue a la URL /Movies . Haga clic en un vínculo Edit (Editar). En el explorador, vea el
código fuente de la página. El código HTML generado para el elemento <form> se muestra abajo.
Los elementos <input> se muestran en un elemento HTML <form> cuyo atributo action se establece para
publicar en la dirección URL /Movies/Edit/id . Los datos del formulario se publicarán en el servidor cuando se
haga clic en el botón Save . La última línea antes del cierre del elemento </form> muestra el token XSRF oculto
generado por la aplicación auxiliar de etiquetas de formulario.
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction("Index");
}
return View(movie);
}
El atributo [ValidateAntiForgeryToken] valida el token XSRF oculto generado por el generador de tokens
antifalsificación en la herramienta auxiliar de etiquetas de formulario.
El sistema de enlace de modelos toma los valores de formulario publicados y crea un objeto Movie que se pasa
como el parámetro movie . El método ModelState.IsValid comprueba que los datos presentados en el formulario
pueden usarse para modificar (editar o actualizar) un objeto Movie . Si los datos son válidos, se guardan. Los
datos de película actualizados (o modificados) se guardan en la base de datos mediante una llamada al método
SaveChangesAsync del contexto de base de datos. Después de guardar los datos, el código redirige al usuario al
método de acción Index de la clase MoviesController , que muestra la colección de películas, incluidos los
cambios que se acaban de hacer.
Antes de que el formulario se envíe al servidor, la validación del lado cliente comprueba cualquier regla de
validación en los campos. Si hay errores de validación, se muestra un mensaje de error y no se publica el
formulario. Si JavaScript está deshabilitado, no dispondrá de la validación del lado cliente, sino que el servidor
detectará los valores publicados que no son válidos y los valores de formulario se volverán a mostrar con
mensajes de error. Más adelante en el tutorial se examina la validación de modelos con más detalle. La aplicación
auxiliar de etiquetas de validación en la plantilla de vista Views/Movies/Edit.cshtml se encarga de mostrar los
mensajes de error correspondientes.
Todos los métodos HttpGet del controlador de películas siguen un patrón similar. Obtienen un objeto de película
(o una lista de objetos, en el caso de Index ) y pasan el objeto (modelo) a la vista. El método Create pasa un
objeto de película vacío a la vista Create . Todos los métodos que crean, editan, eliminan o modifican los datos lo
hacen en la sobrecarga [HttpPost] del método. La modificación de datos en un método HTTP GET supone un
riesgo de seguridad. La modificación de datos en un método HTTP GET también infringe procedimientos
recomendados de HTTP y el patrón de arquitectura REST, que especifica que las solicitudes GET no deben
cambiar el estado de la aplicación. En otras palabras, realizar una operación GET debería ser una operación segura
sin efectos secundarios, que no modifica los datos persistentes.
Recursos adicionales
Globalización y localización
Introducción a las aplicaciones auxiliares de etiquetas
Creación de aplicaciones auxiliares de etiquetas
Prevención de ataques de falsificación de solicitudes
Protección del controlador frente al exceso de publicación
ViewModels
Aplicación auxiliar de etiquetas de formulario
Aplicación auxiliar de etiquetas de entrada
Aplicación auxiliar de etiquetas de elementos de etiqueta
Aplicación auxiliar de etiquetas de selección
Aplicación auxiliar de etiquetas de validación
A N T E R IO R : T R A B A J A R C O N S IG U IE N T E : A G R E G A R
S Q L IT E BÚSQUEDA
Adición de una búsqueda en una aplicación de
ASP.NET Core MVC
25/06/2018 • 12 minutes to read • Edit Online
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
La primera línea del método de acción Index crea una consulta LINQ para seleccionar las películas:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
El código s => s.Title.Contains() anterior es una expresión Lambda. Las lambdas se usan en consultas LINQ
basadas en métodos como argumentos para métodos de operador de consulta estándar, tales como el método
Where o Contains (usado en el código anterior). Las consultas LINQ no se ejecutan cuando se definen ni cuando
se modifican mediante una llamada a un método, como Where , Contains u OrderBy . En su lugar, se aplaza la
ejecución de la consulta. Esto significa que la evaluación de una expresión se aplaza hasta que su valor realizado se
repita realmente o se llame al método ToListAsync . Para más información sobre la ejecución de consultas en
diferido, vea Ejecución de la consulta.
Nota: El método Contains se ejecuta en la base de datos, no en el código de c# que se muestra arriba. La
distinción entre mayúsculas y minúsculas en la consulta depende de la base de datos y la intercalación. En SQL
Server, Contains se asigna a SQL LIKE, que distingue entre mayúsculas y minúsculas. En SQLite, con la
intercalación predeterminada, se distingue entre mayúsculas y minúsculas.
Navegue a /Movies/Index . Anexe una cadena de consulta como ?searchString=Ghost a la dirección URL. Se
muestran las películas filtradas.
Si se cambia la firma del método Index para que tenga un parámetro con el nombre id , el parámetro id
coincidirá con el marcador {id} opcional para el conjunto de rutas predeterminado en Startup.cs.
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
Nota: SQLlite distingue mayúsculas de minúsculas, por lo que tendrá que buscar "Ghost" y no "ghost".
El método Index anterior:
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (!String.IsNullOrEmpty(id))
{
movies = movies.Where(s => s.Title.Contains(id));
}
Ahora puede pasar el título de la búsqueda como datos de ruta (un segmento de dirección URL ) en lugar de como
un valor de cadena de consulta.
Sin embargo, no se puede esperar que los usuarios modifiquen la dirección URL cada vez que quieran buscar una
película. Por tanto, ahora deberá agregar elementos de la interfaz de usuario con los que podrán filtrar las
películas. Si cambió la firma del método Index para probar cómo pasar el parámetro ID enlazado a una ruta,
vuelva a cambiarlo para que tome un parámetro denominado searchString :
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
La etiqueta HTML <form> usa la aplicación auxiliar de etiquetas de formulario, por lo que cuando se envía el
formulario, la cadena de filtro se registra en la acción Index del controlador de películas. Guarde los cambios y
después pruebe el filtro.
No hay ninguna sobrecarga [HttpPost] del método Index como cabría esperar. No es necesario, porque el
método no cambia el estado de la aplicación, simplemente filtra los datos.
Después, puede agregar el método [HttpPost] Index siguiente.
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
El parámetro notUsed se usa para crear una sobrecarga para el método Index . Hablaremos sobre esto más
adelante en el tutorial.
Si agrega este método, el invocador de acción coincidiría con el método [HttpPost] Index , mientras que el
método [HttpPost] Index se ejecutaría tal como se muestra en la imagen de abajo.
Sin embargo, aunque agregue esta versión de [HttpPost] al método Index , hay una limitación en cómo se ha
implementado todo esto. Supongamos que quiere marcar una búsqueda en particular o que quiere enviar un
vínculo a sus amigos donde puedan hacer clic para ver la misma lista filtrada de películas. Tenga en cuenta que la
dirección URL de la solicitud HTTP POST es la misma que la dirección URL de la solicitud GET
(localhost:xxxxx/Movies/Index): no hay información de búsqueda en la URL. La información de la cadena de
búsqueda se envía al servidor como un valor de campo de formulario. Puede comprobarlo con las herramientas
de desarrollo del explorador o con la excelente herramienta Fiddler. En la imagen de abajo se muestran las
herramientas de desarrollo del explorador Chrome:
Puede ver el parámetro de búsqueda y el token XSRF en el cuerpo de la solicitud. Tenga en cuenta, como se
mencionó en el tutorial anterior, que la aplicación auxiliar de etiquetas de formulario genera un token XSRF
antifalsificación. Como no se van a modificar datos, no es necesario validar el token con el método del controlador.
El parámetro de búsqueda se encuentra en el cuerpo de solicitud y no en la dirección URL. Por eso no se puede
capturar dicha información para marcarla o compartirla con otros usuarios. Para solucionar este problema,
especificaremos que la solicitud sea HTTP GET .
Cambie la etiqueta <form> en la vista de Razor Views\movie\Index.cshtml para especificar method="get" :
<form asp-controller="Movies" asp-action="Index" method="get">
Ahora, cuando se envía una búsqueda, la URL contiene la cadena de consulta de búsqueda. La búsqueda también
será dirigida al método de acción HttpGet Index , aunque tenga un método HttpPost Index .
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace MvcMovie.Models
{
public class MovieGenreViewModel
{
public List<Movie> movies;
public SelectList genres;
public string movieGenre { get; set; }
}
}
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (!String.IsNullOrEmpty(movieGenre))
{
movies = movies.Where(x => x.Genre == movieGenre);
}
return View(movieGenreVM);
}
El código siguiente es una consulta LINQ que recupera todos los géneros de la base de datos.
La SelectList de géneros se crea mediante la proyección de los distintos géneros (no queremos que nuestra lista
de selección tenga géneros duplicados).
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.movies[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.movies[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.movies[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.movies[0].Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.movies)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.ID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Examine la expresión lambda usada en la siguiente aplicación auxiliar HTML:
@Html.DisplayNameFor(model => model.movies[0].Title)
En el código anterior, la aplicación auxiliar HTML DisplayNameFor inspecciona la propiedad Title a la que se
hace referencia en la expresión lambda para determinar el nombre para mostrar. Puesto que la expresión lambda
se inspecciona en lugar de evaluarse, no recibirá una infracción de acceso cuando model , model.movies o
model.movies[0] sean null o estén vacíos. Cuando se evalúa la expresión lambda (por ejemplo,
@Html.DisplayFor(modelItem => item.Title) ), se evalúan los valores de propiedad del modelo.
A N T E R IO R : V IS T A S Y M É T O D O S D E S IG U IE N T E : A G R E G A R U N
C ON TROL A D OR CAM PO
Adición de un nuevo campo
25/06/2018 • 5 minutes to read • Edit Online
[Bind("ID,Title,ReleaseDate,Genre,Price,Rating")]
También necesita actualizar las plantillas de vista para mostrar, crear y editar la nueva propiedad Rating en la
vista del explorador.
Edite el archivo /Views/Movies/Index.cshtml y agregue un campo Rating :
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.movies[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.movies[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.movies[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.movies[0].Price)
</th>
<th>
@Html.DisplayNameFor(model => model.movies[0].Rating)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.movies)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.Rating)
</td>
<td>
Este error se muestra porque la clase del modelo Movie actualizada es diferente del esquema de la tabla Movie de
la base de datos existente. (No hay ninguna columna Rating en la tabla de la base de datos).
Este error se puede resolver de varias maneras:
1. Desconecte la base de datos y haga que Entity Framework vuelva a crear automáticamente la base de datos
según el nuevo esquema de clase de modelo. Con este enfoque se pierden los datos que tenga en la base
de datos, así que no puede hacer esto con una base de datos de producción. A menudo, una forma
productiva de desarrollar una aplicación consiste en usar un inicializador para propagar una base de datos
con datos de prueba.
2. Modifique manualmente el esquema de la base de datos existente para que coincida con las clases de
modelo. La ventaja de este enfoque es que se conservan los datos. Puede realizar este cambio de forma
manual o mediante la creación de un script de cambio de base de datos.
3. Use Migraciones de Code First para actualizar el esquema de la base de datos.
Para este tutorial, se desconectará la base de datos y se volverá a crear cuando cambie el esquema. Para
desconectar la base de datos, ejecute este comando desde un terminal:
dotnet ef database drop
Actualice la clase SeedData para que proporcione un valor para la nueva columna. A continuación se muestra un
cambio de ejemplo, aunque es conveniente realizarlo con cada new Movie .
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-1-11"),
Genre = "Romantic Comedy",
Rating = "R",
Price = 7.99M
},
A N T E R IO R : A G R E G A R S IG U IE N T E : A G R E G A R
BÚSQUEDA V A L ID A C IÓ N
Adición de validación
25/06/2018 • 17 minutes to read • Edit Online
Actualice la clase Movie para aprovechar los atributos de validación integrados Required , StringLength ,
RegularExpression y Range .
[!code-csharp]
[!code-csharp]
Los atributos de validación especifican el comportamiento que quiere aplicar en las propiedades del modelo al
que se aplican. Los atributos Required y MinimumLength indican que una propiedad debe tener un valor, pero nada
evita que un usuario escriba espacios en blanco para satisfacer esta validación. El atributo RegularExpression se
usa para limitar los caracteres que se pueden escribir. En el código anterior, Genre y Rating solamente pueden
usar letras (no se permiten espacios en blanco, números ni caracteres especiales). El atributo Range restringe un
valor a un intervalo determinado. El atributo StringLength permite establecer la longitud máxima de una
propiedad de cadena y, opcionalmente, su longitud mínima. Los tipos de valor (como decimal , int , float ,
DateTime ) son intrínsecamente necesarios y no necesitan el atributo [Required] .
Cuando ASP.NET aplica automáticamente reglas de validación, logramos que la aplicación sea más sólida.
También nos permite asegurarnos de que todo se valida y que no nos dejamos ningún dato incorrecto en la base
de datos accidentalmente.
Observe cómo el formulario presenta automáticamente un mensaje de error de validación adecuado en cada
campo que contiene un valor no válido. Los errores se aplican en el lado cliente (con JavaScript y jQuery) y en el
lado servidor (cuando un usuario tiene JavaScript deshabilitado).
Una ventaja importante es que no fue necesario cambiar ni una sola línea de código en la clase MoviesController
o en la vista Create.cshtml para habilitar esta interfaz de usuario de validación. El controlador y las vistas que creó
en pasos anteriores de este tutorial seleccionaron automáticamente las reglas de validación que especificó
mediante atributos de validación en las propiedades de la clase del modelo Movie . Pruebe la aplicación mediante
el método de acción Edit y se aplicará la misma validación.
Los datos del formulario no se enviarán al servidor hasta que dejen de producirse errores de validación de cliente.
Puede comprobarlo colocando un punto de interrupción en el método HTTP Post mediante la herramienta Fiddler
o las herramientas de desarrollo F12.
// GET: Movies/Create
public IActionResult Create()
{
return View();
}
// POST: Movies/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
[Bind("ID,Title,ReleaseDate,Genre,Price, Rating")] Movie movie)
{
if (ModelState.IsValid)
{
_context.Add(movie);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(movie);
}
El primer método de acción Create (HTTP GET) muestra el formulario de creación inicial. La segunda versión (
[HttpPost] ) controla el envío de formulario. El segundo método Create (la versión [HttpPost] ) llama a
ModelState.IsValid para comprobar si la película tiene errores de validación. Al llamar a este método se evalúan
todos los atributos de validación que se hayan aplicado al objeto. Si el objeto tiene errores de validación, el
método Create vuelve a mostrar el formulario. Si no hay ningún error, el método guarda la nueva película en la
base de datos. En nuestro ejemplo de película, el formulario no se publica en el servidor si se detectan errores de
validación del lado cliente; cuando hay errores de validación en el lado cliente, no se llama nunca al segundo
método Create . Si deshabilita JavaScript en el explorador, se deshabilita también la validación del cliente y puede
probar si el método Create HTTP POST ModelState.IsValid detecta errores de validación.
Puede establecer un punto de interrupción en el método [HttpPost] Create y comprobar si nunca se llama al
método. La validación del lado cliente no enviará los datos del formulario si se detectan errores de validación. Si
deshabilita JavaScript en el explorador y después envía el formulario con errores, se alcanzará el punto de
interrupción. Puede seguir obteniendo validación completa sin JavaScript.
En la siguiente imagen se muestra cómo deshabilitar JavaScript en el explorador Firefox.
En la siguiente imagen se muestra cómo deshabilitar JavaScript en el explorador Chrome.
Después de deshabilitar JavaScript, publique los datos no válidos y siga los pasos del depurador.
Abajo se muestra una parte de la plantilla de vista Create.cshtml a la que se aplicó scaffolding en un paso anterior
de este tutorial. Los métodos de acción que se muestran arriba la usan para mostrar el formulario inicial y para
volver a mostrarlo en caso de error.
<form asp-action="Create">
<div class="form-horizontal">
<h4>Movie</h4>
<hr />
La aplicación auxiliar de etiquetas de entrada usa los atributos DataAnnotations y genera los atributos HTML
necesarios para la validación de jQuery en el lado cliente. La aplicación auxiliar de etiquetas de validación muestra
errores de validación. Para más información, vea Introduction to model validation in ASP.NET Core MVC
(Introducción a la validación de modelos en ASP.NET Core MVC ).
Lo realmente bueno de este enfoque es que ni el controlador ni la plantilla de vista Create saben que las reglas
de validación actuales se están aplicando ni conocen los mensajes de error específicos que se muestran. Las reglas
de validación y las cadenas de error solo se especifican en la clase Movie . Estas mismas reglas de validación se
aplican automáticamente a la vista Edit y a cualquier otra vista de plantillas creada que edite el modelo.
Cuando necesite cambiar la lógica de validación, puede hacerlo exactamente en un solo lugar mediante la adición
de atributos de validación al modelo (en este ejemplo, la clase Movie ). No tendrá que preocuparse de que
diferentes partes de la aplicación sean incoherentes con el modo en que se aplican las reglas: toda la lógica de
validación se definirá en un solo lugar y se usará en todas partes. Esto mantiene el código muy limpio y hace que
sea fácil de mantener y evolucionar. También significa que respeta totalmente el principio DRY.
[Range(1, 100)]
[DataType(DataType.Currency)]
public decimal Price { get; set; }
Los atributos DataType solo proporcionan sugerencias para que el motor de vista aplique formato a los datos (y
ofrece atributos o elementos como <a> para las direcciones URL y <a href="mailto:EmailAddress.com"> para el
correo electrónico). Use el atributo RegularExpression para validar el formato de los datos. El atributo DataType
no es un atributo de validación, sino que se usa para especificar un tipo de datos más específico que el tipo
intrínseco de la base de datos. En este caso solo queremos realizar un seguimiento de la fecha, no la hora. La
enumeración DataType proporciona muchos tipos de datos, como Date (Fecha), Time (Hora), PhoneNumber
(Número de teléfono), Currency (Moneda), EmailAddress (Dirección de correo electrónico), etc. El atributo
DataType también puede permitir que la aplicación proporcione automáticamente características específicas del
tipo. Por ejemplo, se puede crear un vínculo mailto: para DataType.EmailAddress y se puede proporcionar un
selector de datos para DataType.Date en exploradores compatibles con HTML5. Los atributos DataType emiten
atributos HTML 5 data- (se pronuncia "datos dash") que los exploradores HTML 5 pueden comprender. Los
atributos DataType no proporcionan ninguna validación.
DataType.Date no especifica el formato de la fecha que se muestra. De manera predeterminada, el campo de
datos se muestra según los formatos predeterminados basados en el elemento CultureInfo del servidor.
El atributo DisplayFormat se usa para especificar el formato de fecha de forma explícita:
El valor ApplyFormatInEditMode especifica que el formato se debe aplicar también cuando el valor se muestra en
un cuadro de texto para su edición. En algunos campos este comportamiento puede no ser conveniente. Por poner
un ejemplo, es probable que con valores de moneda no se quiera que el símbolo de la divisa se incluya en el
cuadro de texto editable.
El atributo DisplayFormat puede usarse por sí solo, pero normalmente se recomienda usar el atributo DataType .
El atributo DataType transmite la semántica de los datos en contraposición a cómo se representa en una pantalla
y ofrece las siguientes ventajas que no proporciona DisplayFormat:
El explorador puede habilitar características de HTML5 (por ejemplo, mostrar un control de calendario, el
símbolo de moneda adecuado según la configuración regional, vínculos de correo electrónico, etc.).
De manera predeterminada, el explorador representa los datos con el formato correcto según la
configuración regional.
El atributo DataType puede habilitar MVC para que elija la plantilla de campo adecuada para representar
los datos ( DisplayFormat , si se usa por sí solo, usa la plantilla de cadena).
NOTE
La validación de jQuery no funciona con el atributo Range ni DateTime . Por ejemplo, el código siguiente siempre muestra
un error de validación del lado cliente, incluso cuando la fecha está en el intervalo especificado:
[Range(typeof(DateTime), "1/1/1966", "1/1/2020")]
Debe deshabilitar la validación de fechas de jQuery para usar el atributo Range con DateTime . Por lo general no
se recomienda compilar fechas fijas en los modelos, así que desaconseja usar el atributo Range y DateTime .
El código siguiente muestra la combinación de atributos en una línea:
[!code-csharp]
[!code-csharp]
En la siguiente parte de la serie de tutoriales, revisaremos la aplicación y realizaremos algunas mejoras a los
métodos Details y Delete generados automáticamente.
Recursos adicionales
Trabajar con formularios
Globalización y localización
Introducción a las aplicaciones auxiliares de etiquetas
Creación de aplicaciones auxiliares de etiquetas
A N T E R IO R : A G R E G A R U N S IG U IE N T E : E X A M IN A R L O S M É T O D O S D E T A IL S Y
CAM PO D E L E TE
Examinar los métodos Details y Delete de una
aplicación ASP.NET Core
25/06/2018 • 5 minutes to read • Edit Online
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
// GET: Movies/Delete/5
public async Task<IActionResult> Delete(int? id)
{
// POST: Movies/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
Common Language Runtime (CLR ) requiere métodos sobrecargados para disponer de una firma de parámetro
única (mismo nombre de método, pero lista de parámetros diferente). En cambio, aquí necesita dos métodos
Delete (uno para GET y otro para POST ) que tienen la misma firma de parámetro (ambos deben aceptar un
número entero como parámetro).
Hay dos enfoques para este problema. Uno consiste en proporcionar nombres diferentes a los métodos, que es
lo que hizo el mecanismo de scaffolding en el ejemplo anterior. Pero esto implica un pequeño problema:
ASP.NET asigna segmentos de una dirección URL a los métodos de acción por nombre y, si cambia el nombre
de un método, normalmente el enrutamiento no podría encontrar ese método. La solución es la que ve en el
ejemplo, que consiste en agregar el atributo ActionName("Delete") al método DeleteConfirmed . Ese atributo
efectúa la asignación para el sistema de enrutamiento para que una dirección URL que incluya /Delete/ para
una solicitud POST busque el método DeleteConfirmed .
Otra solución alternativa común para los métodos que tienen nombres y firmas idénticos consiste en cambiar la
firma del método POST artificialmente para incluir un parámetro adicional (sin usar). Es lo que hicimos en una
publicación anterior, cuando agregamos el parámetro notUsed . Podría hacer lo mismo aquí para el método
[HttpPost] Delete :
// POST: Movies/Delete/6
[ValidateAntiForgeryToken]
public async Task<IActionResult> Delete(int id, bool notUsed)
Publicar en Azure
Vea Publicar una aplicación web de ASP.NET Core en Azure App Service con Visual Studio para obtener
instrucciones sobre cómo publicar esta aplicación en Azure con Visual Studio. También se puede publicar la
aplicación desde la línea de comandos.
A N T E R IO R
Crear una API web con ASP.NET Core y Visual
Studio para Mac
25/06/2018 • 25 minutes to read • Edit Online
Información general
En este tutorial se crea la siguiente API:
GET /api/todo Obtener todas las tareas Ninguna Matriz de tareas pendientes
pendientes
El cliente es todo lo que consume la API web (aplicación móvil, explorador, etcétera). En este tutorial no se
crea ningún cliente. Postman o curl se utilizan como el cliente para probar la aplicación.
Un modelo es un objeto que representa los datos de la aplicación. En este caso, el único modelo es una
tarea pendiente. Los modelos se representan como clases de C#, también conocidas como clases POCO
(del inglés Plain Old C# Object, objetos CRL antiguos sin formato).
Un controlador es un objeto que controla solicitudes HTTP y crea la respuesta HTTP. Esta aplicación tiene
un único controlador.
Para simplificar el tutorial, la aplicación no usa una base de datos persistente. La aplicación de ejemplo
almacena tareas pendientes en una base de datos en memoria.
Vea Introduction to ASP.NET Core MVC on macOS or Linux (Introducción a ASP.NET Core MVC en macOS o
Linux) para obtener un ejemplo en el que se usa una base de datos persistente.
Requisitos previos
Visual Studio for Mac
Crear el proyecto
En Visual Studio, seleccione Archivo > Nueva solución.
Seleccione Aplicación .NET Core > API web de ASP.NET Core > Siguiente.
Escriba TodoApi en Nombre del proyecto y haga clic en Crear.
Iniciar la aplicación
En Visual Studio, seleccione Ejecutar > Iniciar con depuración para iniciar la aplicación. Visual Studio inicia
un explorador y se desplaza a http://localhost:5000 . Obtendrá un error HTTP 404 (No encontrado). Cambie la
dirección URL a http://localhost:<port>/api/values . Se muestran los datos de ValuesController :
["value1","value2"]
Agregar compatibilidad con Entity Framework Core
Instale el proveedor de base de datos Entity Framework Core InMemory. Este proveedor de base de datos
permite usar Entity Framework Core con una base de datos en memoria.
En el menú Proyecto, seleccione Agregar paquetes NuGet.
Como alternativa, puede hacer clic con el botón derecho en Dependencias y seleccionar Agregar
paquetes.
Escriba EntityFrameworkCore.InMemory en el cuadro de búsqueda.
Seleccione Microsoft.EntityFrameworkCore.InMemory y, luego, Agregar paquete.
Agregar una clase de modelo
Un modelo es un objeto que representa los datos de la aplicación. En este caso, el único modelo es una tarea
pendiente.
En el Explorador de soluciones, haga clic con el botón derecho en el proyecto. Seleccione Agregar > Nueva
carpeta. Asigne a la carpeta el nombre Modelos.
NOTE
Puede colocar clases de modelo en cualquier lugar del proyecto, pero la carpeta Models se usa por convención.
Haga clic con el botón derecho en la carpeta Modelos y seleccione Agregar > Nuevo archivo > General >
Clase vacía. Denomine la clase TodoItem y, después, haga clic en Nuevo.
Reemplace el código generado por el siguiente:
namespace TodoApi.Models
{
public class TodoItem
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsComplete { get; set; }
}
}
using Microsoft.EntityFrameworkCore;
namespace TodoApi.Models
{
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}
Agregar un controlador
En el Explorador de soluciones, en la carpeta Controladores, agregue la clase TodoController .
Reemplace el código generado con el siguiente:
[!code-csharp]
El código anterior define una clase de controlador de API sin métodos. En las secciones siguientes, se agregan
métodos para implementar la API.
[!code-csharp]
El código anterior define una clase de controlador de API sin métodos. En las secciones siguientes, se agregan
métodos para implementar la API. La clase se anota con un atributo [ApiController] para habilitar algunas
características muy prácticas. Para más información sobre las características que el atributo habilita, vea
Anotación de una clase con ApiControllerAttribute.
El constructor del controlador usa la inserción de dependencias para insertar el contexto de base de datos (
TodoContext ) en el controlador. El contexto de base de datos se usa en cada uno de los métodos CRUD del
controlador. El constructor agrega un elemento a la base de datos en memoria si no existe ninguno.
Tareas pendientes
Para obtener tareas pendientes, agregue estos métodos a la clase TodoController :
[!code-csharp]
[!code-csharp]
Estos métodos implementan los dos métodos GET:
GET /api/todo
GET /api/todo/{id}
[
{
"id": 1,
"name": "Item1",
"isComplete": false
}
]
Más adelante en el tutorial, veremos cómo se puede ver la respuesta HTTP por medio de Postman o curl.
Enrutamiento y rutas URL
El atributo [HttpGet] indica un método que responde a una solicitud HTTP GET. La ruta de dirección URL para
cada método se construye como sigue:
Tome la cadena de plantilla en el atributo Route del controlador:
[!code-csharp]
[!code-csharp]
Reemplace [controller] por el nombre del controlador, que es el nombre de clase de controlador sin el
sufijo "Controller". En este ejemplo, el nombre de clase de controlador es TodoController y el nombre de raíz
es "todo". El enrutamiento en ASP.NET Core no distingue entre mayúsculas y minúsculas.
Si el atributo [HttpGet] tiene una plantilla de ruta (como [HttpGet("/products")] ), anexiónela a la ruta de
acceso. En este ejemplo no se usa una plantilla. Para más información, vea Enrutamiento mediante atributos
con atributos Http[Verb].
En el siguiente método GetById , "{id}" es una variable de marcador de posición correspondiente al
identificador único de la tarea pendiente. Cuando GetById se invoca, asigna el valor "{id}" de la dirección URL
al parámetro id del método.
[!code-csharp]
[!code-csharp]
Name = "GetTodo" crea una ruta con nombre. Rutas con nombre:
Permita que la aplicación cree un vínculo HTTP mediante el nombre de ruta.
Se explican más adelante en el tutorial.
Valores devueltos
El método GetAll devuelve una colección de objetos TodoItem . MVC serializa automáticamente el objeto a
JSON y escribe el JSON en el cuerpo del mensaje de respuesta. El código de respuesta para este método es
200, suponiendo que no haya ninguna excepción no controlada. Las excepciones no controladas se convierten
en errores 5xx.
En cambio, el método GetById devuelve el tipo más general IActionResult, que representa una amplia gama de
tipos de valor devuelto. GetById tiene dos tipos de valor devueltos distintos:
Si no hay ningún elemento que coincida con el identificador solicitado, el método devuelve un error 404.
Devolver NotFound genera una respuesta HTTP 404.
En caso contrario, el método devuelve 200 con un cuerpo de respuesta JSON. Devolver Ok genera una
respuesta HTTP 200.
En cambio, el método GetById devuelve el tipo ActionResult<T>, que representa una amplia gama de tipos de
valor devuelto. GetById tiene dos tipos de valor devueltos distintos:
Si no hay ningún elemento que coincida con el identificador solicitado, el método devuelve un error 404.
Devolver NotFound genera una respuesta HTTP 404.
En caso contrario, el método devuelve 200 con un cuerpo de respuesta JSON. Devolver item genera una
respuesta HTTP 200.
Iniciar la aplicación
En Visual Studio, seleccione Ejecutar > Iniciar con depuración para iniciar la aplicación. Visual Studio inicia
un explorador y navega hasta http://localhost:<port> , donde <port> es un número de puerto elegido
aleatoriamente. Obtendrá un error HTTP 404 (No encontrado). Cambie la dirección URL a
http://localhost:<port>/api/values . Se muestran los datos de ValuesController :
["value1","value2"]
[{"key":1,"name":"Item1","isComplete":false}]
TIP
Si no aparece ninguna respuesta tras hacer clic en Send (Enviar), deshabilite la opción SSL certification verification
(Comprobación de certificación SSL). La encontrará en File > Settings (Archivo > Configuración). Vuelva a hacer clic en el
botón Send (Enviar) después de deshabilitar la configuración.
Haga clic en la pestaña Headers (Encabezados) del panel Response (Respuesta) y copie el valor de encabezado
de Location (Ubicación):
Puede usar el URI del encabezado Location (Ubicación) para tener acceso al recurso que ha creado. El método
Create devuelve CreatedAtRoute. El primer parámetro que se pasa a CreatedAtRoute representa la ruta con
nombre que se usa para generar la dirección URL. Recuerde que el método GetById creó la ruta con nombre
"GetTodo" :
Actualizar
[!code-csharp]
[!code-csharp]
Update es similar a Create , pero usa HTTP PUT. La respuesta es 204 Sin contenido. Según la especificación
HTTP, una solicitud PUT requiere que el cliente envíe toda la entidad actualizada, no solo los deltas. Para admitir
actualizaciones parciales, use HTTP PATCH.
{
"key": 1,
"name": "walk dog",
"isComplete": true
}
Eliminar
[HttpDelete("{id}")]
public IActionResult Delete(long id)
{
var todo = _context.TodoItems.Find(id);
if (todo == null)
{
return NotFound();
}
_context.TodoItems.Remove(todo);
_context.SaveChanges();
return NoContent();
}
Agregue un archivo HTML denominado index.html al directorio wwwroot del proyecto. Reemplace el contenido
por el siguiente marcado:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>To-do CRUD</title>
<style>
input[type='submit'], button, [aria-label] {
cursor: pointer;
}
#spoiler {
display: none;
}
table {
font-family: Arial, sans-serif;
border: 1px solid;
border-collapse: collapse;
}
th {
background-color: #0066CC;
color: white;
}
td {
border: 1px solid;
padding: 5px;
}
</style>
</head>
<body>
<h1>To-do CRUD</h1>
<h3>Add</h3>
<form action="javascript:void(0);" method="POST" onsubmit="addItem()">
<input type="text" id="add-name" placeholder="New to-do">
<input type="submit" value="Add">
</form>
<div id="spoiler">
<h3>Edit</h3>
<form class="my-form">
<input type="hidden" id="edit-id">
<input type="checkbox" id="edit-isComplete">
<input type="text" id="edit-name">
<input type="submit" value="Edit">
<a onclick="closeInput()" aria-label="Close">✖</a>
</form>
</div>
<p id="counter"></p>
<table>
<tr>
<th>Is Complete</th>
<th>Name</th>
<th></th>
<th></th>
</tr>
<tbody id="todos"></tbody>
</table>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script src="site.js"></script>
</body>
</html>
Agregue un archivo JavaScript denominado site.js al directorio wwwroot del proyecto. Reemplace el contenido
por el siguiente código:
$(document).ready(function () {
getData();
});
function getData() {
$.ajax({
type: 'GET',
url: uri,
success: function (data) {
$('#todos').empty();
getCount(data.length);
$.each(data, function (key, item) {
const checked = item.isComplete ? 'checked' : '';
todos = data;
}
});
}
function addItem() {
const item = {
'name': $('#add-name').val(),
'isComplete': false
};
$.ajax({
type: 'POST',
accepts: 'application/json',
url: uri,
contentType: 'application/json',
data: JSON.stringify(item),
error: function (jqXHR, textStatus, errorThrown) {
alert('here');
},
success: function (result) {
getData();
$('#add-name').val('');
}
});
}
function deleteItem(id) {
$.ajax({
url: uri + '/' + id,
type: 'DELETE',
success: function (result) {
getData();
}
});
}
function editItem(id) {
function editItem(id) {
$.each(todos, function (key, item) {
if (item.id === id) {
$('#edit-name').val(item.name);
$('#edit-id').val(item.id);
$('#edit-isComplete').val(item.isComplete);
}
});
$('#spoiler').css({ 'display': 'block' });
}
$('.my-form').on('submit', function () {
const item = {
'name': $('#edit-name').val(),
'isComplete': $('#edit-isComplete').is(':checked'),
'id': $('#edit-id').val()
};
$.ajax({
url: uri + '/' + $('#edit-id').val(),
type: 'PUT',
accepts: 'application/json',
contentType: 'application/json',
data: JSON.stringify(item),
success: function (result) {
getData();
}
});
closeInput();
return false;
});
function closeInput() {
$('#spoiler').css({ 'display': 'none' });
}
Puede que sea necesario realizar un cambio en la configuración de inicio del proyecto de ASP.NET Core para
comprobar la página HTML localmente. Abra launchSettings.json en el directorio Properties del proyecto. Quite
la propiedad launchUrl para forzar a la aplicación a abrirse en index.html, esto es, el archivo predeterminado del
proyecto.
Existen varias formas de obtener jQuery. En el fragmento de código anterior, la biblioteca se carga desde una red
CDN. Este ejemplo es un ejemplo de CRUD completo de llamada a la API de jQuery. Existen más características
en este ejemplo para hacer que la experiencia sea más completa. Aquí verá algunas explicaciones sobre las
llamadas a la API.
Obtener una lista de tareas pendientes
Para obtener una lista de tareas pendientes, envíe una solicitud HTTP GET a /api/todo.
La función de JQuery ajax envía una solicitud AJAX a la API, que devuelve código JSON que representa un
objeto o una matriz. Esta función puede controlar todas las formas de interacción de HTTP enviando una
solicitud HTTP a la url especificada. GET se emite como type . La función de devolución de llamada success
se invoca si la solicitud se realiza correctamente. En la devolución de llamada, el DOM se actualiza con la
información de la tarea pendiente.
$(document).ready(function () {
getData();
});
function getData() {
$.ajax({
type: 'GET',
url: uri,
success: function (data) {
$('#todos').empty();
getCount(data.length);
$.each(data, function (key, item) {
const checked = item.isComplete ? 'checked' : '';
todos = data;
}
});
}
function addItem() {
const item = {
'name': $('#add-name').val(),
'isComplete': false
};
$.ajax({
type: 'POST',
accepts: 'application/json',
url: uri,
contentType: 'application/json',
data: JSON.stringify(item),
error: function (jqXHR, textStatus, errorThrown) {
alert('here');
},
success: function (result) {
getData();
$('#add-name').val('');
}
});
}
$.ajax({
url: uri + '/' + $('#edit-id').val(),
type: 'PUT',
accepts: 'application/json',
contentType: 'application/json',
data: JSON.stringify(item),
success: function (result) {
getData();
}
});
$.ajax({
url: uri + '/' + id,
type: 'DELETE',
success: function (result) {
getData();
}
});
Pasos siguientes
Para más información sobre cómo usar una base de datos persistente, vea:
Creación de una aplicación web de páginas de Razor con ASP.NET Core
Trabajo con datos en ASP.NET Core
Páginas de ayuda de ASP.NET Core Web API mediante Swagger
Enrutamiento a acciones del controlador
Compilación de API web con ASP.NET Core
Tipos de valor devuelto de acción del controlador
Para obtener información sobre la implementación de una API, como en Azure App Service, vea la
documentación sobre Hospedaje e implementación.
Vea o descargue el código de ejemplo. Vea cómo descargarlo.
Crear una API web con ASP.NET Core y Visual
Studio Code
17/05/2018 • 25 minutes to read • Edit Online
Información general
En este tutorial se crea la siguiente API:
GET /api/todo Obtener todas las tareas Ninguna Matriz de tareas pendientes
pendientes
El cliente es todo lo que consume la API web (aplicación móvil, explorador, etcétera). En este tutorial no se
crea ningún cliente. Postman o curl se utilizan como el cliente para probar la aplicación.
Un modelo es un objeto que representa los datos de la aplicación. En este caso, el único modelo es una
tarea pendiente. Los modelos se representan como clases de C#, también conocidas como clases POCO
(del inglés Plain Old C# Object, objetos CRL antiguos sin formato).
Un controlador es un objeto que controla solicitudes HTTP y crea la respuesta HTTP. Esta aplicación tiene
un único controlador.
Para simplificar el tutorial, la aplicación no usa una base de datos persistente. La aplicación de ejemplo
almacena tareas pendientes en una base de datos en memoria.
Requisitos previos
Install the following:
.NET Core SDK 2.0 or later
Visual Studio Code
C# for Visual Studio Code
.NET Core 2.1 SDK or later
Visual Studio Code
C# for Visual Studio Code
Crear el proyecto
Desde una consola, ejecute los siguientes comandos:
La carpeta TodoApi se abre en Visual Studio Code (VS Code). Seleccione el archivo Startup.cs.
Seleccione Sí en el mensaje de advertencia "Required assets to build and debug are missing from
'TodoApi'. Add them?" (Faltan los activos necesarios para compilar y depurar en 'TodoApi'. ¿Quiere
agregarlos?).
Seleccione Restaurar en el mensaje de información "There are unresolved dependencies" (Hay
dependencias no resueltas).
Presione Depurar (F5) para compilar y ejecutar el programa. En un navegador, vaya a
http://localhost:5000/api/values. Se muestra el siguiente resultado:
["value1","value2"]
Vea Ayuda de Visual Studio Code para obtener sugerencias sobre el uso de VS Code.
using Microsoft.EntityFrameworkCore;
namespace TodoApi.Models
{
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}
Adición de un controlador
En la carpeta Controladores, cree una clase denominada TodoController . Reemplace el contenido por el
siguiente código:
[!code-csharp]
El código anterior define una clase de controlador de API sin métodos. En las secciones siguientes, se agregan
métodos para implementar la API.
[!code-csharp]
El código anterior define una clase de controlador de API sin métodos. En las secciones siguientes, se agregan
métodos para implementar la API. La clase se anota con un atributo [ApiController] para habilitar algunas
características muy prácticas. Para más información sobre las características que el atributo habilita, vea
Anotación de una clase con ApiControllerAttribute.
El constructor del controlador usa la inserción de dependencias para insertar el contexto de base de datos (
TodoContext ) en el controlador. El contexto de base de datos se usa en cada uno de los métodos CRUD del
controlador. El constructor agrega un elemento a la base de datos en memoria si no existe ninguno.
Tareas pendientes
Para obtener tareas pendientes, agregue estos métodos a la clase TodoController :
[!code-csharp]
[!code-csharp]
Estos métodos implementan los dos métodos GET:
GET /api/todo
GET /api/todo/{id}
[
{
"id": 1,
"name": "Item1",
"isComplete": false
}
]
Más adelante en el tutorial, veremos cómo se puede ver la respuesta HTTP por medio de Postman o curl.
Enrutamiento y rutas URL
El atributo [HttpGet] indica un método que responde a una solicitud HTTP GET. La ruta de dirección URL para
cada método se construye como sigue:
Tome la cadena de plantilla en el atributo Route del controlador:
[!code-csharp]
[!code-csharp]
Reemplace [controller] por el nombre del controlador, que es el nombre de clase de controlador sin el
sufijo "Controller". En este ejemplo, el nombre de clase de controlador es TodoController y el nombre de raíz
es "todo". El enrutamiento en ASP.NET Core no distingue entre mayúsculas y minúsculas.
Si el atributo [HttpGet] tiene una plantilla de ruta (como [HttpGet("/products")] ), anexiónela a la ruta de
acceso. En este ejemplo no se usa una plantilla. Para más información, vea Enrutamiento mediante atributos
con atributos Http[Verb].
En el siguiente método GetById , "{id}" es una variable de marcador de posición correspondiente al
identificador único de la tarea pendiente. Cuando GetById se invoca, asigna el valor "{id}" de la dirección
URL al parámetro id del método.
[!code-csharp]
[!code-csharp]
Name = "GetTodo" crea una ruta con nombre. Rutas con nombre:
Permita que la aplicación cree un vínculo HTTP mediante el nombre de ruta.
Se explican más adelante en el tutorial.
Valores devueltos
El método GetAll devuelve una colección de objetos TodoItem . MVC serializa automáticamente el objeto a
JSON y escribe el JSON en el cuerpo del mensaje de respuesta. El código de respuesta para este método es
200, suponiendo que no haya ninguna excepción no controlada. Las excepciones no controladas se convierten
en errores 5xx.
En cambio, el método GetById devuelve el tipo más general IActionResult, que representa una amplia gama de
tipos de valor devuelto. GetById tiene dos tipos de valor devueltos distintos:
Si no hay ningún elemento que coincida con el identificador solicitado, el método devuelve un error 404.
Devolver NotFound genera una respuesta HTTP 404.
En caso contrario, el método devuelve 200 con un cuerpo de respuesta JSON. Devolver Ok genera una
respuesta HTTP 200.
En cambio, el método GetById devuelve el tipo ActionResult<T>, que representa una amplia gama de tipos de
valor devuelto. GetById tiene dos tipos de valor devueltos distintos:
Si no hay ningún elemento que coincida con el identificador solicitado, el método devuelve un error 404.
Devolver NotFound genera una respuesta HTTP 404.
En caso contrario, el método devuelve 200 con un cuerpo de respuesta JSON. Devolver item genera una
respuesta HTTP 200.
Iniciar la aplicación
En VS Code, presione F5 para iniciar la aplicación. Vaya a http://localhost:5000/api/todo (el controlador Todo
que se acaba de crear).
Agregue un archivo HTML denominado index.html al directorio wwwroot del proyecto. Reemplace el contenido
por el siguiente marcado:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>To-do CRUD</title>
<style>
input[type='submit'], button, [aria-label] {
cursor: pointer;
}
#spoiler {
display: none;
}
table {
font-family: Arial, sans-serif;
border: 1px solid;
border-collapse: collapse;
}
th {
background-color: #0066CC;
color: white;
}
td {
border: 1px solid;
padding: 5px;
}
</style>
</head>
<body>
<h1>To-do CRUD</h1>
<h3>Add</h3>
<form action="javascript:void(0);" method="POST" onsubmit="addItem()">
<input type="text" id="add-name" placeholder="New to-do">
<input type="submit" value="Add">
</form>
<div id="spoiler">
<h3>Edit</h3>
<form class="my-form">
<input type="hidden" id="edit-id">
<input type="checkbox" id="edit-isComplete">
<input type="text" id="edit-name">
<input type="submit" value="Edit">
<a onclick="closeInput()" aria-label="Close">✖</a>
</form>
</div>
<p id="counter"></p>
<table>
<tr>
<th>Is Complete</th>
<th>Name</th>
<th></th>
<th></th>
</tr>
<tbody id="todos"></tbody>
</table>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script src="site.js"></script>
</body>
</html>
Agregue un archivo JavaScript denominado site.js al directorio wwwroot del proyecto. Reemplace el contenido
por el siguiente código:
$(document).ready(function () {
getData();
});
function getData() {
$.ajax({
type: 'GET',
url: uri,
success: function (data) {
$('#todos').empty();
getCount(data.length);
$.each(data, function (key, item) {
const checked = item.isComplete ? 'checked' : '';
todos = data;
}
});
}
function addItem() {
const item = {
'name': $('#add-name').val(),
'isComplete': false
};
$.ajax({
type: 'POST',
accepts: 'application/json',
url: uri,
contentType: 'application/json',
data: JSON.stringify(item),
error: function (jqXHR, textStatus, errorThrown) {
alert('here');
},
success: function (result) {
getData();
$('#add-name').val('');
}
});
}
function deleteItem(id) {
$.ajax({
$.ajax({
url: uri + '/' + id,
type: 'DELETE',
success: function (result) {
getData();
}
});
}
function editItem(id) {
$.each(todos, function (key, item) {
if (item.id === id) {
$('#edit-name').val(item.name);
$('#edit-id').val(item.id);
$('#edit-isComplete').val(item.isComplete);
}
});
$('#spoiler').css({ 'display': 'block' });
}
$('.my-form').on('submit', function () {
const item = {
'name': $('#edit-name').val(),
'isComplete': $('#edit-isComplete').is(':checked'),
'id': $('#edit-id').val()
};
$.ajax({
url: uri + '/' + $('#edit-id').val(),
type: 'PUT',
accepts: 'application/json',
contentType: 'application/json',
data: JSON.stringify(item),
success: function (result) {
getData();
}
});
closeInput();
return false;
});
function closeInput() {
$('#spoiler').css({ 'display': 'none' });
}
Puede que sea necesario realizar un cambio en la configuración de inicio del proyecto de ASP.NET Core para
comprobar la página HTML localmente. Abra launchSettings.json en el directorio Properties del proyecto. Quite
la propiedad launchUrl para forzar a la aplicación a abrirse en index.html, esto es, el archivo predeterminado
del proyecto.
Existen varias formas de obtener jQuery. En el fragmento de código anterior, la biblioteca se carga desde una
red CDN. Este ejemplo es un ejemplo de CRUD completo de llamada a la API de jQuery. Existen más
características en este ejemplo para hacer que la experiencia sea más completa. Aquí verá algunas explicaciones
sobre las llamadas a la API.
Obtener una lista de tareas pendientes
Para obtener una lista de tareas pendientes, envíe una solicitud HTTP GET a /api/todo.
La función de JQuery ajax envía una solicitud AJAX a la API, que devuelve código JSON que representa un
objeto o una matriz. Esta función puede controlar todas las formas de interacción de HTTP enviando una
solicitud HTTP a la url especificada. GET se emite como type . La función de devolución de llamada success
se invoca si la solicitud se realiza correctamente. En la devolución de llamada, el DOM se actualiza con la
información de la tarea pendiente.
$(document).ready(function () {
getData();
});
function getData() {
$.ajax({
type: 'GET',
url: uri,
success: function (data) {
$('#todos').empty();
getCount(data.length);
$.each(data, function (key, item) {
const checked = item.isComplete ? 'checked' : '';
todos = data;
}
});
}
function addItem() {
const item = {
'name': $('#add-name').val(),
'isComplete': false
};
$.ajax({
type: 'POST',
accepts: 'application/json',
url: uri,
contentType: 'application/json',
data: JSON.stringify(item),
error: function (jqXHR, textStatus, errorThrown) {
alert('here');
},
success: function (result) {
getData();
$('#add-name').val('');
}
});
}
$.ajax({
url: uri + '/' + $('#edit-id').val(),
type: 'PUT',
accepts: 'application/json',
contentType: 'application/json',
data: JSON.stringify(item),
success: function (result) {
getData();
}
});
$.ajax({
url: uri + '/' + id,
type: 'DELETE',
success: function (result) {
getData();
}
});
[!code-csharp]
[!code-csharp]
Usar Postman para enviar una solicitud de creación
Inicia la aplicación.
Abra Postman.
Actualice el número de puerto en la dirección URL de localhost.
Establezca el método HTTP en POST.
Haga clic en la pestaña Body (Cuerpo).
Seleccione el botón de radio Raw (Sin formato).
Establezca el tipo en JSON (application/json).
Escriba un cuerpo de solicitud con una tarea pendiente parecida al siguiente JSON:
{
"name":"walk dog",
"isComplete":true
}
TIP
Si no aparece ninguna respuesta tras hacer clic en Send (Enviar), deshabilite la opción SSL certification verification
(Comprobación de certificación SSL). La encontrará en File > Settings (Archivo > Configuración). Vuelva a hacer clic en el
botón Send (Enviar) después de deshabilitar la configuración.
Haga clic en la pestaña Headers (Encabezados) del panel Response (Respuesta) y copie el valor de encabezado
de Location (Ubicación):
El URI del encabezado de ubicación puede utilizarse para acceder al nuevo elemento.
Actualizar
Agregue el siguiente método Update :
[!code-csharp]
[!code-csharp]
Update es similar a Create , salvo por el hecho de que usa HTTP PUT. La respuesta es 204 Sin contenido.
Según la especificación HTTP, una solicitud PUT requiere que el cliente envíe toda la entidad actualizada, no solo
los deltas. Para admitir actualizaciones parciales, use HTTP PATCH.
Use Postman para actualizar el nombre de la tarea pendiente a "walk cat":
Eliminar
Agregue el siguiente método Delete :
[HttpDelete("{id}")]
public IActionResult Delete(long id)
{
var todo = _context.TodoItems.Find(id);
if (todo == null)
{
return NotFound();
}
_context.TodoItems.Remove(todo);
_context.SaveChanges();
return NoContent();
}
Pasos siguientes
Para más información sobre cómo usar una base de datos persistente, vea:
Creación de una aplicación web de páginas de Razor con ASP.NET Core
Trabajo con datos en ASP.NET Core
Páginas de ayuda de ASP.NET Core Web API mediante Swagger
Enrutamiento a acciones del controlador
Compilación de API web con ASP.NET Core
Tipos de valor devuelto de acción del controlador
Para obtener información sobre la implementación de una API, como en Azure App Service, vea la
documentación sobre Hospedaje e implementación.
Vea o descargue el código de ejemplo. Vea cómo descargarlo.
Desarrollar aplicaciones ASP.NET Core con dotnet
watch
14/05/2018 • 4 minutes to read • Edit Online
dotnet run
La salida de la consola muestra mensajes similares al siguiente (indicando que la aplicación se ejecuta y espera
solicitudes):
$ dotnet run
Hosting environment: Development
Content root path: C:/Docs/aspnetcore/tutorials/dotnet-watch/sample/WebApp
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="2.0.0" />
</ItemGroup>
dotnet restore
Ejecución de los comandos de la CLI de .NET Core mediante
dotnet watch
Cualquier comando de la CLI de .NET Core se puede ejecutar con dotnet watch . Por ejemplo:
Ejecute dotnet watch run en la carpeta WebApp. La salida de la consola indica que se ha iniciado watch .
Guarde el archivo. La salida de la consola muestra que dotnet watch ha detectado un cambio de archivo y ha
reiniciado la aplicación.
Compruebe que http://localhost:<port number>/api/math/product?a=4&b=5 devuelve el resultado correcto.
5. Corrija el código del método Product para que devuelva el producto. Guarde el archivo.
dotnet watch detecta el cambio de archivo y vuelve a ejecutar las pruebas. La salida de la consola indica que se
han superado las pruebas.
dotnet-watch en GitHub
dotnet-watch forma parte del repositorio de DotNetTools de GitHub.
En la sección MSBuild del archivo Léame de dotnet-watch se describe cómo se puede configurar dotnet-watch
desde el archivo de proyecto de MSBuild que se está inspeccionando. El archivo Léame de dotnet-watch contiene
información de dotnet-watch que no se trata en este tutorial.
Crear servicios back-end para aplicaciones móviles
nativas con ASP.NET Core
25/06/2018 • 14 minutes to read • Edit Online
Al pulsar un elemento en la pantalla de la lista principal se abre un cuadro de diálogo de edición, donde se puede
modificar el nombre del elemento, las notas y la configuración de Done (Listo), o se puede eliminar el elemento:
Este ejemplo está configurado para usar de forma predeterminada servicios back-end hospedados en
developer.xamarin.com, lo que permite operaciones de solo lectura. Para probarlo usted mismo con la aplicación de
ASP.NET Core que creó en la siguiente sección que se ejecuta en el equipo, debe actualizar la constante RestUrl de
la aplicación. Vaya hasta el proyecto ToDoREST y abra el archivo Constants.cs. Reemplace RestUrl con una
dirección URL que incluya la dirección IP de su equipo (no localhost ni 127.0.0.1, puesto que esta dirección se usa
desde el emulador de dispositivo, no desde el equipo). Incluya también el número de puerto (5000). Para
comprobar que los servicios funcionan con un dispositivo, asegúrese de que no tiene un firewall activo que bloquea
el acceso a este puerto.
NOTE
Asegúrese de que ejecuta la aplicación directamente, en lugar de tras IIS Express, que omite las solicitudes no locales de forma
predeterminada. Ejecute dotnet run desde un símbolo del sistema o elija el perfil del nombre de aplicación en la lista
desplegable de destino de depuración en la barra de herramientas de Visual Studio.
Agregue una clase de modelo para representar las tareas pendientes. Marque los campos obligatorios mediante el
atributo [Required] :
using System.ComponentModel.DataAnnotations;
namespace ToDoApi.Models
{
public class ToDoItem
{
[Required]
public string ID { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Notes { get; set; }
Los métodos de API necesitan alguna manera de trabajar con los datos. Use la misma interfaz de IToDoRepository
que usa el ejemplo original de Xamarin:
using System.Collections.Generic;
using ToDoApi.Models;
namespace ToDoApi.Interfaces
{
public interface IToDoRepository
{
bool DoesItemExist(string id);
IEnumerable<ToDoItem> All { get; }
ToDoItem Find(string id);
void Insert(ToDoItem item);
void Update(ToDoItem item);
void Delete(string id);
}
}
using System.Collections.Generic;
using System.Linq;
using ToDoApi.Interfaces;
using ToDoApi.Models;
namespace ToDoApi.Services
{
public class ToDoRepository : IToDoRepository
{
private List<ToDoItem> _toDoList;
public ToDoRepository()
{
InitializeData();
}
_toDoList.Add(todoItem1);
_toDoList.Add(todoItem2);
_toDoList.Add(todoItem3);
}
}
}
services.AddSingleton<IToDoRepository,ToDoRepository>();
}
TIP
Obtenga más información sobre cómo crear API web en Cree su primera API web con ASP.NET Core MVC y Visual Studio.
Crear el controlador
Agregue un nuevo controlador para el proyecto: ToDoItemsController. Debe heredar de
Microsoft.AspNetCore.Mvc.Controller. Agregue un atributo Route para indicar que el controlador controlará las
solicitudes realizadas a las rutas de acceso que comiencen con api/todoitems . El token [controller] de la ruta se
sustituye por el nombre del controlador (si se omite el sufijo Controller ) y es especialmente útil para las rutas
globales. Obtenga más información sobre el enrutamiento.
El controlador necesita un IToDoRepository para funcionar. Solicite una instancia de este tipo a través del
constructor del controlador. En tiempo de ejecución, esta instancia se proporcionará con la compatibilidad del
marco con la inserción de dependencias.
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using ToDoApi.Interfaces;
using ToDoApi.Models;
namespace ToDoApi.Controllers
{
[Route("api/[controller]")]
public class ToDoItemsController : Controller
{
private readonly IToDoRepository _toDoRepository;
Esta API es compatible con cuatro verbos HTTP diferentes para realizar operaciones CRUD (creación, lectura,
actualización, eliminación) en el origen de datos. La más simple de ellas es la operación de lectura, que corresponde
a una solicitud HTTP GET.
Leer elementos
La solicitud de una lista de elementos se realiza con una solicitud GET al método List . El atributo [HttpGet] en el
método List indica que esta acción solo debe controlar las solicitudes GET. La ruta de esta acción es la ruta
especificada en el controlador. No es necesario usar el nombre de acción como parte de la ruta. Solo debe
asegurarse de que cada acción tiene una ruta única e inequívoca. El enrutamiento de atributos se puede aplicar
tanto a los niveles de controlador como de método para crear rutas específicas.
[HttpGet]
public IActionResult List()
{
return Ok(_toDoRepository.All);
}
El método List devuelve un código de respuesta 200 OK y todos los elementos de lista de tareas, serializados
como JSON.
Puede probar el nuevo método de API con una variedad de herramientas, como Postman, que se muestra a
continuación:
Crear elementos
Por convención, la creación de elementos de datos se asigna al verbo HTTP POST. El método Create tiene un
atributo [HttpPost] aplicado y acepta una instancia ToDoItem . Puesto que el argumento item se pasará en el
cuerpo de la solicitud POST, este parámetro se decora con el atributo [FromBody] .
Dentro del método, se comprueba la validez del elemento y si existió anteriormente en el almacén de datos y, si no
hay problemas, se agrega mediante el repositorio. Al comprobar ModelState.IsValid se realiza una validación de
modelos, y debe realizarse en cada método de API que acepte datos proporcionados por usuario.
[HttpPost]
public IActionResult Create([FromBody] ToDoItem item)
{
try
{
if (item == null || !ModelState.IsValid)
{
return BadRequest(ErrorCode.TodoItemNameAndNotesRequired.ToString());
}
bool itemExists = _toDoRepository.DoesItemExist(item.ID);
if (itemExists)
{
return StatusCode(StatusCodes.Status409Conflict, ErrorCode.TodoItemIDInUse.ToString());
}
_toDoRepository.Insert(item);
}
catch (Exception)
{
return BadRequest(ErrorCode.CouldNotCreateItem.ToString());
}
return Ok(item);
}
El ejemplo usa una enumeración que contiene códigos de error que se pasan al cliente móvil:
Pruebe a agregar nuevos elementos con Postman eligiendo el verbo POST que proporciona el nuevo objeto en
formato JSON en el cuerpo de la solicitud. También debe agregar un encabezado de solicitud que especifica un
Content-Type de application/json .
El método devuelve el elemento recién creado en la respuesta.
Actualizar elementos
La modificación de registros se realiza mediante solicitudes HTTP PUT. Aparte de este cambio, el método Edit es
casi idéntico a Create . Tenga en cuenta que, si no se encuentra el registro, la acción Edit devolverá una respuesta
NotFound (404 ).
[HttpPut]
public IActionResult Edit([FromBody] ToDoItem item)
{
try
{
if (item == null || !ModelState.IsValid)
{
return BadRequest(ErrorCode.TodoItemNameAndNotesRequired.ToString());
}
var existingItem = _toDoRepository.Find(item.ID);
if (existingItem == null)
{
return NotFound(ErrorCode.RecordNotFound.ToString());
}
_toDoRepository.Update(item);
}
catch (Exception)
{
return BadRequest(ErrorCode.CouldNotUpdateItem.ToString());
}
return NoContent();
}
Para probar con Postman, cambie el verbo a PUT. Especifique los datos actualizados del objeto en el cuerpo de la
solicitud.
Este método devuelve una respuesta NoContent (204) cuando se realiza correctamente, para mantener la
coherencia con la API existente.
Eliminar elementos
La eliminación de registros se consigue mediante solicitudes DELETE al servicio y pasando el identificador del
elemento que va a eliminar. Al igual que con las actualizaciones, las solicitudes para elementos que no existen
recibirán respuestas NotFound . De lo contrario, las solicitudes correctas recibirán una respuesta NoContent (204).
[HttpDelete("{id}")]
public IActionResult Delete(string id)
{
try
{
var item = _toDoRepository.Find(id);
if (item == null)
{
return NotFound(ErrorCode.RecordNotFound.ToString());
}
_toDoRepository.Delete(id);
}
catch (Exception)
{
return BadRequest(ErrorCode.CouldNotDeleteItem.ToString());
}
return NoContent();
}
Tenga en cuenta que, al probar la funcionalidad de eliminar, no se necesita nada en el cuerpo de la solicitud.
Una aplicación de ASP.NET Core es una aplicación de consola que crea un servidor web en su método Main :
ASP.NET Core 2.x
ASP.NET Core 1.x
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
namespace aspnetcoreapp
{
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
El método Main invoca a WebHost.CreateDefaultBuilder , que sigue el patrón de generador para crear un host de
aplicación web. El generador tiene métodos que definen el servidor web (por ejemplo, UseKestrel ) y la clase de
inicio ( UseStartup ). En el ejemplo anterior, se asigna automáticamente el servidor web Kestrel. El host web de
ASP.NET Core intenta ejecutarse en IIS, si está disponible. Otros servidores web, como HTTP.sys, se pueden usar al
invocar el método de extensión adecuado. UseStartup se explica en la sección siguiente.
IWebHostBuilder, el tipo de valor devuelto de la invocación WebHost.CreateDefaultBuilder , proporciona muchos
métodos opcionales. Algunos de estos métodos incluyen UseHttpSys para hospedar la aplicación en HTTP.sys y
UseContentRoot para especificar el directorio de contenido raíz. Los métodos Build y Run crean el objeto
IWebHost que hospeda la aplicación y empieza a escuchar las solicitudes HTTP.
Inicio
El método UseStartup de WebHostBuilder especifica la clase Startup para la aplicación:
ASP.NET Core 2.x
ASP.NET Core 1.x
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
La clase Startup es donde se define la canalización de control de solicitudes y donde se configuran los servicios
necesarios para la aplicación. La clase Startup debe ser pública y contener los siguientes métodos:
ConfigureServices define los servicios que usa la aplicación (por ejemplo, ASP.NET Core MVC, Entity Framework
Core, Identity). Configure define el software intermedio en la canalización de solicitudes.
Para obtener más información, vea Application startup (Inicio de la aplicación).
Raíz web
La raíz web de una aplicación es el directorio del proyecto que contiene recursos públicos y estáticos, como
archivos de imagen, CSS y JavaScript.
Entornos
Los entornos, como "Desarrollo" y "Producción", son un concepto de primera clase en ASP.NET Core y se pueden
establecer mediante variables de entorno.
Para obtener más información, consulte Uso de varios entornos.
Configuración
ASP.NET Core usa un modelo de configuración basado en pares de nombre-valor. El modelo de configuración no
se basa en System.Configuration o web.config. La configuración obtiene valores de un conjunto ordenado de
proveedores de configuración. Los proveedores de configuración integrados admiten una variedad de formatos de
archivo (XML, JSON, INI) y variables de entorno para habilitar la configuración basada en el entorno. También
puede escribir sus propios proveedores de configuración personalizados.
Para obtener más información, vea Configuración.
Registro
ASP.NET Core es compatible con una API de registro que funciona con una variedad de proveedores de registro.
Los proveedores integrados admiten el envío de registros a uno o varios destinos. Se pueden usar plataformas de
registro de terceros.
Registro
Control de errores
ASP.NET Core tiene características integradas para controlar los errores en las aplicaciones, incluida una página de
excepciones de desarrollador, páginas de errores personalizados, páginas de códigos de estado estáticos y control
de excepciones de inicio.
Para más información, vea Introducción al control de errores.
Enrutamiento
ASP.NET Core ofrece características para el enrutamiento de solicitudes de aplicación a los controladores de ruta.
Para más información, vea Enrutamiento.
Proveedores de archivos
ASP.NET Core abstrae el acceso al sistema de archivos mediante el uso de proveedores de archivos, lo que ofrece
una interfaz común para trabajar con archivos entre plataformas.
Para más información, vea Proveedores de archivos.
Archivos estáticos
El software intermedio de archivos estáticos trabaja con archivos estáticos (por ejemplo, HTML, CSS, imágenes y
JavaScript).
Para obtener más información, consulte Archivos estáticos.
Hospedaje
Las aplicaciones ASP.NET Core configuran e inician un host. Dicho host es responsable de la administración de
inicio y duración de la aplicación.
Para obtener más información, consulte Hospedaje en ASP.NET Core.
Servidores
El modelo de hospedaje de ASP.NET Core no escucha directamente las solicitudes. El modelo de hospedaje se basa
en una implementación de servidor HTTP para reenviar la solicitud a la aplicación. La solicitud reenviada se
empaqueta como un conjunto de objetos de característica al que se puede tener acceso a través de interfaces.
ASP.NET Core incluye un servidor web administrado multiplataforma, denominado Kestrel. Kestrel se suele
ejecutar detrás de un servidor web de producción como IIS o Nginx. Kestrel se puede ejecutar como un servidor
perimetral.
Para más información, vea Servidores y los temas siguientes:
Kestrel
Módulo ASP.NET Core
HTTP.sys (anteriormente denominado WebListener)
Globalización y localización
El hecho de crear un sitio web multilingüe con ASP.NET Core permite que este llegue a un público más amplio.
ASP.NET Core proporciona servicios y software intermedio para la localización en diferentes idiomas y referencias
culturales.
Para más información, vea Globalización y localización.
Características de la solicitud
Los detalles de implementación del servidor web relacionados con las solicitudes HTTP y las respuestas se definen
en las interfaces. En las implementaciones del servidor web y el software intermedio se usan estas interfaces para
crear y modificar la canalización de hospedaje de la aplicación.
Para más información, vea Características de la solicitud.
WebSockets
WebSocket es un protocolo que habilita canales de comunicación bidireccional persistentes a través de conexiones
TCP. Se usa para aplicaciones de chat, tableros de cotizaciones, juegos y donde se necesite funcionalidad en tiempo
real en una aplicación web. ASP.NET Core es compatible con características de socket web.
Para más información, vea WebSockets.
Metapaquete Microsoft.AspNetCore.All
El metapaquete Microsoft.AspNetCore.All para ASP.NET Core incluye lo siguiente:
Todos los paquetes admitidos por el equipo de ASP.NET Core.
Todos los paquetes admitidos por Entity Framework Core.
Dependencias internas y de terceros usadas por ASP.NET Core y Entity Framework Core.
Para más información, vea Metapaquete Microsoft.AspNetCore.All.
Clase Startup
Las aplicaciones de ASP.NET Core utilizan una clase Startup , que se denomina Startup por convención. La
clase Startup :
Puede incluir opcionalmente un método ConfigureServices para configurar los servicios de la aplicación.
Debe incluir un método Configure para crear la canalización de procesamiento de solicitudes de la
aplicación.
El runtime llama a ConfigureServices y Configure al iniciarse la aplicación:
El constructor de clase Startup acepta dependencias definidas por el host. Un uso común de la inserción de
dependencias en la clase Startup consiste en insertar:
IHostingEnvironment para configurar servicios según el entorno;
IConfiguration para configurar la aplicación durante el inicio.
public class Startup
{
public Startup(IHostingEnvironment env, IConfiguration config)
{
HostingEnvironment = env;
Configuration = config;
}
Método ConfigureServices
Características del método ConfigureServices:
Optional
Lo llama el host de web antes del método Configure para configurar los servicios de la aplicación.
Es donde se establecen por convención las opciones de configuración.
La adición de servicios al contenedor de servicios hace que estén disponibles en la aplicación y en el método
Configure . Los servicios se resuelven mediante inserción de dependencias o desde
IApplicationBuilder.ApplicationServices.
El host de web puede configurar algunos servicios antes de que se llame a los métodos Startup . Los
detalles están disponibles en el tema Hospedaje en ASP.NET Core.
Para las características que requieren una configuración sustancial, hay métodos de extensión Add[Service]
en IServiceCollection. Una aplicación web típica registra los servicios de Entity Framework, Identity y MVC:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
El método Configure
El método Configure se utiliza para especificar la forma en que la aplicación responde a las solicitudes HTTP.
La canalización de solicitudes se configura mediante la adición de componentes de middleware a una
instancia de IApplicationBuilder. IApplicationBuilder está disponible para el método Configure , pero no
está registrado en el contenedor de servicios. Hospedaje crea un IApplicationBuilder y lo pasa
directamente a Configure (origen de referencia).
Las plantillas ASP.NET Core configuran la canalización con compatibilidad para una página de excepción
para desarrolladores, BrowserLink, páginas de error, archivos estáticos y ASP.NET MVC:
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
}
Cada método de extensión Use agrega un componente de middleware a la canalización de solicitudes. Por
ejemplo, el método de extensión UseMvc agrega el middleware de enrutamiento a la canalización de
solicitudes y establece MVC como controlador predeterminado.
Cada componente de middleware de la canalización de solicitudes es responsable de invocar al siguiente
componente de la canalización o de cortocircuitar la cadena en caso de ser necesario. Si el cortocircuito no
se produce a lo largo de la cadena de middleware, cada middleware tiene una segunda oportunidad de
procesar la solicitud antes de que se envíe al cliente.
Servicios adicionales, como IHostingEnvironment y ILoggerFactory , también se pueden especificar en la
firma del método. Cuando se especifican, esos servicios adicionales se insertan si están disponibles.
Para más información sobre cómo usar IApplicationBuilder y el orden de procesamiento de middleware,
vea Middleware.
Métodos de conveniencia
Los métodos de conveniencia ConfigureServices y Configure pueden usarse en lugar de especificar una
clase Startup . Varias llamadas a ConfigureServices se anexan entre sí. Varias llamadas a Configure usan la
última llamada al método.
app.UseMvcWithDefaultRoute();
app.UseStaticFiles();
})
.Build();
}
Filtros de Startup
Use IStartupFilter para configurar el middleware al principio o al final de la canalización de middleware
Configure de una aplicación. IStartupFilter es útil para garantizar que un middleware se ejecuta antes o
después del middleware agregado por bibliotecas al principio o al final de la canalización de procesamiento
de solicitudes de la aplicación.
IStartupFilter implementa un método único, Configure, que recibe y devuelve
Action<IApplicationBuilder> . IApplicationBuilder define una clase para configurar la canalización de
solicitudes de una aplicación. Para más información, vea Creación de una canalización de middleware con
IApplicationBuilder.
Cada IStartupFilter implementa uno o más middleware en la canalización de solicitudes. Los filtros se
invocan en el orden en que se agregaron al contenedor de servicios. Los filtros pueden agregar middleware
antes o después de pasar el control al siguiente filtro, por lo que se anexan al principio o al final de la
canalización de la aplicación.
La aplicación de ejemplo (cómo descargar) muestra cómo se registra un middleware con IStartupFilter . La
aplicación de ejemplo incluye un middleware que establece un valor de opciones de un parámetro de cadena
de consulta:
public RequestSetOptionsMiddleware(
RequestDelegate next, IOptions<AppOptions> injectedOptions)
{
_next = next;
_injectedOptions = injectedOptions;
}
if (!string.IsNullOrWhiteSpace(option))
{
_injectedOptions.Value.Option = WebUtility.HtmlEncode(option);
}
await _next(httpContext);
}
}
Recursos adicionales
Hospedar aplicaciones de WPF
Uso de varios entornos
Middleware
Registro
Configuración
Clase StartupLoader: método FindStartupType (origen de referencia)
Inserción de dependencias en
ASP.NET Core
25/06/2018 • 34 minutes to read • Edit Online
NOTE
Martin Fowler ha escrito un amplio artículo sobre los contenedores de inversión de
control y el patrón de inserción de dependencias. En los Modelos y prácticas de Microsoft
también encontrará una descripción excelente de la inserción de dependencias.
NOTE
Este artículo trata sobre la inserción de dependencias tal como se aplica a todas las
aplicaciones ASP.NET. La inserción de dependencias dentro de controladores de MVC se
trata en Inserción de dependencias y controladores.
A suitable constructor for type 'YourType' couldn't be located. Ensure the type is
concrete and services are registered for all parameters of a public constructor.
(No se pudo encontrar un constructor adecuado para el tipo "SuTipo".
Asegúrese de que el tipo sea concreto y de que los servicios estén registrados
para todos los parámetros de un constructor público.)
Multiple constructors accepting all given argument types have been found in
type 'YourType'. Multiple constructors accepting all given argument types have
been found in type 'YourType'. There should only be one applicable constructor.
(Se han encontrado en el tipo "SuTipo" varios constructores que aceptan todos
los tipos de argumento especificados. Solo debe haber un constructor
aplicable.)
Microsoft.AspNetCore.Hosting.IHostingEnvir Singleton
onment
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.Logging.ILogger<T> Singleton
Microsoft.AspNetCore.Hosting.Builder.IApplic Transitorio
ationBuilderFactory
Microsoft.AspNetCore.Http.IHttpContextFact Transitorio
ory
Microsoft.Extensions.Options.IOptions<T> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Transitorio
Microsoft.Extensions.ObjectPool.ObjectPoolP Singleton
rovider
Microsoft.Extensions.Options.IConfigureOpti Transitorio
ons<T>
TIPO DE SERVICIO PERÍODO DE DURACIÓN
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IApplicationLi Singleton
fetime
// This method gets called by the runtime. Use this method to add services to
the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
TIP
Puede solicitar determinados servicios proporcionados por el marco de trabajo dentro de
métodos Startup mediante sus listas de parámetros. Vea Inicio de la aplicación para
obtener más detalles.
Registrar servicios
Puede registrar sus propios servicios de aplicación de la manera siguiente. El
primer tipo genérico representa el tipo (normalmente una interfaz) que se solicitará
desde el contenedor. El segundo tipo genérico representa el tipo concreto del cual
creará una instancia el contenedor y que se usará para responder a dichas
solicitudes.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
NOTE
Cada método de extensión services.Add<ServiceName> agrega servicios (y
potencialmente los configura). Por ejemplo, services.AddMvc() agrega los servicios que
requiere MVC. Se recomienda que siga esta convención y coloque los métodos de
extensión en el espacio de nombres Microsoft.Extensions.DependencyInjection para
encapsular grupos de registros del servicio.
// GET: /characters/
public IActionResult Index()
{
PopulateCharactersIfNoneExist();
var characters = _characterRepository.ListAll();
return View(characters);
}
using System.Collections.Generic;
using DependencyInjectionSample.Models;
namespace DependencyInjectionSample.Interfaces
{
public interface ICharacterRepository
{
IEnumerable<Character> ListAll();
void Add(Character character);
}
}
NOTE
La manera en que se usa la inserción de dependencias con la clase
CharacterRepository es un modelo general que puede seguir para todos los servicios
de aplicación, no solo en "repositorios" o clases de acceso a datos.
using System.Collections.Generic;
using System.Linq;
using DependencyInjectionSample.Interfaces;
namespace DependencyInjectionSample.Models
{
public class CharacterRepository : ICharacterRepository
{
private readonly ApplicationDbContext _dbContext;
NOTE
El proceso de creación del objeto solicitado, de todos los objetos que necesita y de todos
los objetos que estos necesitan suele denominarse gráfico de objetos. Del mismo modo,
el conjunto colectivo de dependencias que deben resolverse suele denominarse árbol de
dependencias o gráfico de dependencias.
WARNING
Debe ser especialmente cauteloso al resolver un servicio Scoped desde un singleton. Es
probable que en este caso el servicio tenga un estado incorrecto al procesar las
solicitudes posteriores.
WARNING
Si usa un servicio con ámbito en un middleware, inserte el servicio en el método Invoke
o InvokeAsync . No lo inserte a través de la inserción de constructores, porque ello hace
que el servicio se comporte como un singleton.
Singleton
Los servicios de duración de singleton se crean la primera vez que se solicitan (o
cuando se ejecuta ConfigureServices si se especifica ahí una instancia) y todas las
solicitudes posteriores usan la misma instancia. Si la aplicación requiere un
comportamiento de singleton, se recomienda permitir que el contenedor de
servicios administre la duración del servicio, en lugar de implementar el patrón de
diseño de singleton y administrar por sí mismo la duración del objeto en la clase.
Los servicios se pueden registrar con el contenedor de varias maneras. Ya hemos
visto cómo registrar una implementación de servicio con un tipo determinado
mediante la especificación del tipo concreto que se va a usar. Además, se puede
especificar un generador, que se usará para crear la instancia a petición. El tercer
método consiste en especificar directamente la instancia del tipo que se va a usar,
en cuyo caso el contenedor nunca intentará crear una instancia (ni la eliminará).
Para mostrar la diferencia entre estas duraciones y opciones de registro, imagine
una interfaz simple que representa una o varias tareas como una operación con un
identificador único, OperationId . Según cómo se configure la duración de este
servicio, el contenedor proporcionará las mismas instancias o instancias diferentes
del servicio a la clase que lo solicita. Para dejar claro qué duración se solicita,
crearemos un tipo por opción de duración:
using System;
namespace DependencyInjectionSample.Interfaces
{
public interface IOperation
{
Guid OperationId { get; }
}
Estas interfaces se implementan mediante una sola clase, Operation , que acepta un
Guid en su constructor, o usa un Guid nuevo si no se proporciona ninguno.
services.AddScoped<ICharacterRepository, CharacterRepository>();
services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddSingleton<IOperationSingletonInstance>(new
Operation(Guid.Empty));
services.AddTransient<OperationService, OperationService>();
}
using DependencyInjectionSample.Interfaces;
namespace DependencyInjectionSample.Services
{
public class OperationService
{
public IOperationTransient TransientOperation { get; }
public IOperationScoped ScopedOperation { get; }
public IOperationSingleton SingletonOperation { get; }
public IOperationSingletonInstance SingletonInstanceOperation { get; }
Para mostrar a la aplicación las duraciones de los objetos dentro de las solicitudes
individuales independientes y entre estas, el ejemplo incluye un
OperationsController que solicita cada tipo de IOperation , así como un
OperationService . Después, la acción Index muestra todos los valores del
controlador y del servicio OperationId .
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
using Microsoft.AspNetCore.Mvc;
namespace DependencyInjectionSample.Controllers
{
public class OperationsController : Controller
{
private readonly OperationService _operationService;
private readonly IOperationTransient _transientOperation;
private readonly IOperationScoped _scopedOperation;
private readonly IOperationSingleton _singletonOperation;
private readonly IOperationSingletonInstance
_singletonInstanceOperation;
try
{
var serviceContext = services.GetRequiredService<MyScopedService>
();
// Use the context here
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred.");
}
}
host.Run();
}
Servicios de solicitud
Los servicios disponibles en una solicitud de ASP.NET desde HttpContext se
exponen mediante la colección RequestServices .
Los servicios de solicitud representan los servicios que se configuran y se solicitan
como parte de la aplicación. Cuando los objetos especifican dependencias, estas se
cumplen mediante los tipos que se encuentran en RequestServices , no en
ApplicationServices .
NOTE
Se recomienda que solicite las dependencias como parámetros del constructor para
obtener acceso a la colección RequestServices .
NOTE
En la versión 1.0, el contenedor llamaba a Dispose en todos los objetos IDisposable ,
incluidos aquellos que no había creado.
// Add Autofac
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterModule<DefaultModule>();
containerBuilder.Populate(services);
var container = containerBuilder.Build();
return new AutofacServiceProvider(container);
}
NOTE
Cuando se usa un contenedor de inserción de dependencias de terceros, debe cambiar
ConfigureServices para que devuelva IServiceProvider en lugar de void .
Recomendaciones
Cuando trabaje con la inserción de dependencias, tenga en cuenta las
recomendaciones siguientes:
La inserción de dependencias está destinada a objetos que tienen
dependencias complejas. Los controladores, los servicios, los adaptadores y
los repositorios son ejemplos de objetos que podrían agregarse a la
inserción de dependencias.
Evite almacenar datos y configuraciones directamente en la inserción de
dependencias. Por ejemplo, el carro de la compra de un usuario no debería
agregarse al contenedor de servicios. La configuración debe usar el patrón
de opciones. Del mismo modo, evite los objetos de tipo "contenedor de
datos" que solo existen para permitir el acceso a otro objeto. Es mejor
solicitar el elemento real que se necesita mediante la inserción de
dependencias, si es posible.
Evite el acceso estático a los servicios.
Evite la ubicación de servicios en el código de la aplicación.
Evite el acceso estático a HttpContext .
Al igual que sucede con todas las recomendaciones, podría verse en una situación
que le obligue a ignorar alguna de ellas. Por lo que sabemos las excepciones son
muy poco frecuentes, ya que suelen ser casos muy especiales del propio marco de
trabajo.
La inserción de dependencias es una alternativa al uso de patrones de acceso a
objetos estáticos o globales. No podrá aprovechar las ventajas de la inserción de
dependencias si la combina con el acceso a objetos estáticos.
Recursos adicionales
Inserción de dependencias en vistas
Inserción de dependencias en controladores
Inserción de dependencias en controladores de requisitos
Inicio de aplicaciones
Prueba y depuración
Factory-based middleware activation (Activación de middleware basada en
Factory)
Escritura de código limpio en ASP.NET Core con inserción de dependencias
(MSDN )
Preludio del diseño de aplicaciones administradas por contenedor: ¿cuál es el
lugar del contenedor?
Principio de dependencias explícitas
Los contenedores de inversión de control y el patrón de inserción de
dependencias (Fowler)
Middleware de ASP.NET Core
21/06/2018 • 19 minutes to read • Edit Online
¿Qué es el middleware?
El middleware es un software que se ensambla a una canalización de una aplicación para controlar las solicitudes y
las respuestas. Cada componente puede hacer lo siguiente:
Elegir si se pasa la solicitud al siguiente componente de la canalización.
Realizar trabajos antes y después de invocar al siguiente componente de la canalización.
Los delegados de solicitudes se usan para crear la canalización de solicitudes. Estos también controlan las
solicitudes HTTP.
Los delegados de solicitudes se configuran con los métodos de extensión Run, Map y Use. Un delegado de
solicitudes se puede especificar en línea como un método anónimo (denominado middleware en línea) o se puede
definir en una clase reutilizable. Estas clases reutilizables y métodos anónimos en línea son middleware o
componentes de middleware. Cada componente de middleware de la canalización de solicitudes es responsable de
invocar al siguiente componente de la canalización o de cortocircuitar la cadena en caso de ser necesario.
En Migración de módulos HTTP a middleware se explica la diferencia entre las canalizaciones de solicitudes en
ASP.NET Core y ASP.NET 4.x y se proporcionan más ejemplos de middleware.
Cada delegado puede realizar operaciones antes y después del siguiente. También puede decidir no pasar una
solicitud al siguiente delegado, lo que se denomina "cortocircuitar" la canalización de solicitudes. Este proceso es
necesario muchas veces, ya que previene la realización de trabajo innecesario. Por ejemplo, el middleware de
archivos estáticos puede devolver una solicitud para un archivo estático y cortocircuitar el resto de la canalización.
Los delegados que controlan excepciones deben llamarse al principio de la canalización para que puedan capturar
las excepciones que se producen en las fases siguientes de la canalización.
La aplicación ASP.NET Core más sencilla posible configura un solo delegado de solicitudes que controla todas las
solicitudes. En este caso no se incluye una canalización de solicitudes real. En su lugar, solo se llama a una única
función anónima en respuesta a todas las solicitudes HTTP.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
Ordenación
El orden en el que se agregan los componentes de middleware en el método Configure define el orden en el que
se invocarán en las solicitudes y el orden inverso de la respuesta. Este orden es esencial por motivos de seguridad,
rendimiento y funcionalidad.
El método Configure (que se muestra aquí) agrega los siguientes componentes de middleware:
1. Control de errores y excepciones
2. Servidor de archivos estáticos
3. Autenticación
4. MVC
ASP.NET Core 2.x
ASP.NET Core 1.x
app.Map("/map2", HandleMapTest2);
En la siguiente tabla se muestran las solicitudes y las respuestas de http://localhost:1234 con el código anterior:
SOLICITUD RESPUESTA
MapWhen crea una rama de la canalización de solicitudes según el resultado del predicado proporcionado. Se
puede usar cualquier predicado de tipo Func<HttpContext, bool> para asignar solicitudes a nuevas ramas de la
canalización. En el ejemplo siguiente se usa un predicado para detectar la presencia de una branch variable de
cadena de consulta:
En la siguiente tabla se muestran las solicitudes y las respuestas de http://localhost:1234 con el código anterior:
SOLICITUD RESPUESTA
app.Map("/level1/level2", HandleMultiSeg);
Middleware integrado
ASP.NET Core incluye los siguientes componentes de middleware, así como una descripción del orden en el que se
deberían agregar:
Encabezados reenviados Reenvía encabezados con proxy a la Antes de los componentes que
solicitud actual. consumen los campos actualizados
(ejemplos: esquema, host, cliente, IP y
método).
Invalidación del método HTTP Permite que una solicitud POST entrante Antes de los componentes que
invalide el método. consumen el método actualizado.
Redireccionamiento de HTTPS Redireccione todas las solicitudes HTTP a Antes de los componentes que
HTTPS (ASP.NET Core 2.1 o posterior). consumen la dirección URL.
Seguridad de transporte estricta de Middleware de mejora de seguridad que Antes de que se envíen las respuestas y
HTTP (HSTS) agrega un encabezado de respuesta después de los componentes que
especial (ASP.NET Core 2.1 o posterior). modifican las solicitudes (por ejemplo,
encabezados reenviados, reescritura de
URL).
Enrutamiento Define y restringe las rutas de la Terminal para rutas que coincidan.
solicitud.
Archivos estáticos Proporciona compatibilidad con la Terminal si hay una solicitud que
proporción de archivos estáticos y la coincida con archivos.
exploración de directorios.
Reescritura de direcciones URL Proporciona compatibilidad con la Antes de los componentes que
reescritura de direcciones URL y la consumen la dirección URL.
redirección de solicitudes.
Escritura de middleware
El middleware normalmente está encapsulado en una clase y se expone con un método de extensión. Use el
siguiente middleware a modo de ejemplo. En este se establece la referencia cultural de la solicitud actual a partir de
la cadena de solicitud:
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}
}
}
Nota: El código de ejemplo anterior se usa para mostrar la creación de un componente de middleware. Vea
Globalization and localization (Globalización y localización) para ver la compatibilidad con localización integrada de
ASP.NET Core.
Puede probar el middleware pasando la referencia cultural, por ejemplo, http://localhost:7997/?culture=no .
El código siguiente mueve el delegado de middleware a una clase:
using Microsoft.AspNetCore.Http;
using System.Globalization;
using System.Threading.Tasks;
namespace Culture
{
public class RequestCultureMiddleware
{
private readonly RequestDelegate _next;
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
NOTE
En ASP.NET Core 1.x, el nombre del método del middleware Task debe ser Invoke . En ASP.NET Core 2.0 o posterior, el
nombre puede ser Invoke o InvokeAsync .
using Microsoft.AspNetCore.Builder;
namespace Culture
{
public static class RequestCultureMiddlewareExtensions
{
public static IApplicationBuilder UseRequestCulture(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestCultureMiddleware>();
}
}
}
}
}
El middleware debería seguir el principio de dependencias explicitas mediante la exposición de sus dependencias
en el constructor. El middleware se construye una vez por duración de la aplicación. Vea Dependencias bajo
solicitud a continuación si necesita compartir servicios con middleware en una solicitud.
Los componentes de middleware pueden resolver las dependencias de una inserción de dependencias mediante
parámetros del constructor. UseMiddleware<T> también puede aceptar parámetros adicionales directamente.
Dependencias bajo solicitud
Dado que el middleware se construye al inicio de la aplicación y no bajo solicitud, los servicios de duración con
ámbito que usan los constructores de middleware no se comparten con otros tipos insertados mediante
dependencias durante cada solicitud. Si debe compartir un servicio con ámbito entre su middleware y otros tipos,
agregue esos servicios a la signatura del método Invoke . El método Invoke puede aceptar parámetros adicionales
que propaga la inserción de dependencias. Por ejemplo:
Recursos adicionales
Migración de módulos HTTP a middleware
Inicio de aplicaciones
Solicitud de características
Factory-based middleware activation (Activación de middleware basada en Factory)
Activación de middleware con un contenedor de terceros
Middleware de ASP.NET Core
21/06/2018 • 19 minutes to read • Edit Online
¿Qué es el middleware?
El middleware es un software que se ensambla a una canalización de una aplicación para controlar las
solicitudes y las respuestas. Cada componente puede hacer lo siguiente:
Elegir si se pasa la solicitud al siguiente componente de la canalización.
Realizar trabajos antes y después de invocar al siguiente componente de la canalización.
Los delegados de solicitudes se usan para crear la canalización de solicitudes. Estos también controlan
las solicitudes HTTP.
Los delegados de solicitudes se configuran con los métodos de extensión Run, Map y Use. Un delegado
de solicitudes se puede especificar en línea como un método anónimo (denominado middleware en
línea) o se puede definir en una clase reutilizable. Estas clases reutilizables y métodos anónimos en línea
son middleware o componentes de middleware. Cada componente de middleware de la canalización de
solicitudes es responsable de invocar al siguiente componente de la canalización o de cortocircuitar la
cadena en caso de ser necesario.
En Migración de módulos HTTP a middleware se explica la diferencia entre las canalizaciones de
solicitudes en ASP.NET Core y ASP.NET 4.x y se proporcionan más ejemplos de middleware.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
Ordenación
El orden en el que se agregan los componentes de middleware en el método Configure define el orden
en el que se invocarán en las solicitudes y el orden inverso de la respuesta. Este orden es esencial por
motivos de seguridad, rendimiento y funcionalidad.
El método Configure (que se muestra aquí) agrega los siguientes componentes de middleware:
1. Control de errores y excepciones
2. Servidor de archivos estáticos
3. Autenticación
4. MVC
ASP.NET Core 2.x
ASP.NET Core 1.x
app.Map("/map2", HandleMapTest2);
En la siguiente tabla se muestran las solicitudes y las respuestas de http://localhost:1234 con el código
anterior:
SOLICITUD RESPUESTA
MapWhen crea una rama de la canalización de solicitudes según el resultado del predicado
proporcionado. Se puede usar cualquier predicado de tipo Func<HttpContext, bool> para asignar
solicitudes a nuevas ramas de la canalización. En el ejemplo siguiente se usa un predicado para detectar
la presencia de una branch variable de cadena de consulta:
En la siguiente tabla se muestran las solicitudes y las respuestas de http://localhost:1234 con el código
anterior:
SOLICITUD RESPUESTA
app.Map("/level1/level2", HandleMultiSeg);
Middleware integrado
ASP.NET Core incluye los siguientes componentes de middleware, así como una descripción del orden
en el que se deberían agregar:
Encabezados reenviados Reenvía encabezados con proxy a la Antes de los componentes que
solicitud actual. consumen los campos actualizados
(ejemplos: esquema, host, cliente, IP
y método).
Invalidación del método HTTP Permite que una solicitud POST Antes de los componentes que
entrante invalide el método. consumen el método actualizado.
Redireccionamiento de HTTPS Redireccione todas las solicitudes Antes de los componentes que
HTTP a HTTPS (ASP.NET Core 2.1 o consumen la dirección URL.
posterior).
Seguridad de transporte estricta de Middleware de mejora de seguridad Antes de que se envíen las
HTTP (HSTS) que agrega un encabezado de respuestas y después de los
respuesta especial (ASP.NET Core 2.1 componentes que modifican las
o posterior). solicitudes (por ejemplo,
encabezados reenviados, reescritura
de URL).
Enrutamiento Define y restringe las rutas de la Terminal para rutas que coincidan.
solicitud.
Archivos estáticos Proporciona compatibilidad con la Terminal si hay una solicitud que
proporción de archivos estáticos y la coincida con archivos.
exploración de directorios.
Reescritura de direcciones URL Proporciona compatibilidad con la Antes de los componentes que
reescritura de direcciones URL y la consumen la dirección URL.
redirección de solicitudes.
Escritura de middleware
El middleware normalmente está encapsulado en una clase y se expone con un método de extensión.
Use el siguiente middleware a modo de ejemplo. En este se establece la referencia cultural de la solicitud
actual a partir de la cadena de solicitud:
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Use((context, next) =>
{
var cultureQuery = context.Request.Query["culture"];
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}
}
}
Nota: El código de ejemplo anterior se usa para mostrar la creación de un componente de middleware.
Vea Globalization and localization (Globalización y localización) para ver la compatibilidad con
localización integrada de ASP.NET Core.
Puede probar el middleware pasando la referencia cultural, por ejemplo,
http://localhost:7997/?culture=no .
namespace Culture
{
public class RequestCultureMiddleware
{
private readonly RequestDelegate _next;
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
NOTE
En ASP.NET Core 1.x, el nombre del método del middleware Task debe ser Invoke . En ASP.NET Core 2.0 o
posterior, el nombre puede ser Invoke o InvokeAsync .
using Microsoft.AspNetCore.Builder;
namespace Culture
{
public static class RequestCultureMiddlewareExtensions
{
public static IApplicationBuilder UseRequestCulture(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestCultureMiddleware>();
}
}
}
}
}
Recursos adicionales
Migración de módulos HTTP a middleware
Inicio de aplicaciones
Solicitud de características
Factory-based middleware activation (Activación de middleware basada en Factory)
Activación de middleware con un contenedor de terceros
Activación de middleware basada en Factory en
ASP.NET Core
25/06/2018 • 4 minutes to read • Edit Online
IMiddleware
IMiddleware define el middleware para la canalización de solicitudes de la aplicación. El método
InvokeAsync(HttpContext, RequestDelegate) controla las solicitudes y devuelve una Task que representa la
ejecución del middleware.
Middleware activado por convención:
public class ConventionalMiddleware
{
private readonly RequestDelegate _next;
if (!string.IsNullOrWhiteSpace(keyValue))
{
db.Add(new Request()
{
DT = DateTime.UtcNow,
MiddlewareActivation = "ConventionalMiddleware",
Value = keyValue
});
await db.SaveChangesAsync();
}
await _next(context);
}
}
if (!string.IsNullOrWhiteSpace(keyValue))
{
_db.Add(new Request()
{
DT = DateTime.UtcNow,
MiddlewareActivation = "IMiddlewareMiddleware",
Value = keyValue
});
await _db.SaveChangesAsync();
}
await next(context);
}
}
services.AddDbContext<AppDbContext>(options =>
options.UseInMemoryDatabase("InMemoryDb"));
services.AddTransient<IMiddlewareMiddleware>();
services.AddMvc();
}
app.UseConventionalMiddleware();
app.UseIMiddlewareMiddleware();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
}
IMiddlewareFactory
IMiddlewareFactory proporciona métodos para crear middleware. La implementación de Middleware Factory se
registra en el contenedor como un servicio con ámbito.
La implementación IMiddlewareFactory predeterminada, MiddlewareFactory, se encuentra en el paquete
Microsoft.AspNetCore.Http (fuente de referencia).
Recursos adicionales
Middleware
Activación de middleware con un contenedor de terceros
Activación de middleware con un contenedor de
terceros en ASP.NET Core
25/06/2018 • 4 minutes to read • Edit Online
NOTE
En la aplicación de ejemplo se usa Simple Injector única y exclusivamente con fines de demostración. El uso de Simple
Injector no está avalado. Los métodos de activación de middleware descritos en la documentación de Simple Injector y los
problemas de GitHub está recomendado por los responsables de Simple Injector. Para más información, vea la
documentación de Simple Injector y el repositorio de GitHub de Simple Injector.
IMiddlewareFactory
IMiddlewareFactory proporciona métodos para crear middleware.
En la aplicación de ejemplo, se implementa un Middleware Factory para crear una instancia de
SimpleInjectorActivatedMiddleware . Ese Middleware Factory usa el contenedor de Simple Injector para resolver el
middleware:
public class SimpleInjectorMiddlewareFactory : IMiddlewareFactory
{
private readonly Container _container;
IMiddleware
IMiddleware define el middleware para la canalización de solicitudes de la aplicación.
Middleware activado por una implementación de IMiddlewareFactory
(Middleware/SimpleInjectorActivatedMiddleware.cs):
if (!string.IsNullOrWhiteSpace(keyValue))
{
_db.Add(new Request()
{
DT = DateTime.UtcNow,
MiddlewareActivation = "SimpleInjectorActivatedMiddleware",
Value = keyValue
});
await _db.SaveChangesAsync();
}
await next(context);
}
}
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
_container.Register<AppDbContext>(() =>
{
var optionsBuilder = new DbContextOptionsBuilder<DbContext>();
optionsBuilder.UseInMemoryDatabase("InMemoryDb");
return new AppDbContext(optionsBuilder.Options);
}, Lifestyle.Scoped);
_container.Register<SimpleInjectorActivatedMiddleware>();
_container.Verify();
}
app.UseSimpleInjectorActivatedMiddleware();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
}
Recursos adicionales
Middleware
Factory-based middleware activation (Activación de middleware basada en Factory)
Repositorio de GitHub de Simple Injector
Documentación de Simple Injector
Archivos estáticos en ASP.NET Core
25/06/2018 • 15 minutes to read • Edit Online
Se puede acceder a los archivos estáticos a través de una ruta de acceso relativa a la raíz web. Por ejemplo,
la plantilla de proyecto Aplicación web contiene varias carpetas dentro de la carpeta wwwroot:
wwwroot
css
images
js
El formato de URI para acceder a un archivo en la subcarpeta images es
http://<dirección_servidor>/images/<nombre_archivo_imagen>. Por ejemplo,
http://localhost:9189/images/banner3.svg.
ASP.NET Core 2.x
ASP.NET Core 1.x
Si el destino es .NET Framework, agregue el paquete Microsoft.AspNetCore.StaticFiles al proyecto. Si el
destino es .NET Core, el metapaquete Microsoft.AspNetCore.All incluye este paquete.
Configure el middleware, que permite proporcionar archivos estáticos.
Proporcionar archivos dentro de la raíz web
Invoque el método UseStaticFiles dentro de Startup.Configure :
La sobrecarga del método sin parámetros UseStaticFiles marca los archivos en la raíz web como que se
pueden proporcionar. El siguiente marcado hace referencia a wwwroot/images/banner1.svg:
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "MyStaticFiles")),
RequestPath = "/StaticFiles"
});
}
En el código anterior, la jerarquía del directorio MyStaticFiles se expone públicamente a través del
segmento de URI StaticFiles. Una solicitud a http://<dirección_servidor>/StaticFiles/images/banner1.svg
proporciona el archivo banner1.svg.
El siguiente marcado hace referencia a MyStaticFiles/images/banner1.svg:
[Authorize]
public IActionResult BannerImage()
{
var file = Path.Combine(Directory.GetCurrentDirectory(),
"MyStaticFiles", "images", "banner1.svg");
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "images")),
RequestPath = "/MyImages"
});
app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "images")),
RequestPath = "/MyImages"
});
}
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "images")),
RequestPath = "/MyImages"
});
app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "images")),
RequestPath = "/MyImages"
});
}
IMPORTANT
Debe llamarse a UseDefaultFiles antes de a UseStaticFiles para proporcionar el archivo predeterminado.
UseDefaultFiles es un sistema de reescritura de direcciones URL que no proporciona realmente el archivo.
Habilite el middleware de archivos estáticos a través de UseStaticFiles para proporcionar el archivo.
UseFileServer
UseFileServer combina la funcionalidad de UseStaticFiles , UseDefaultFiles y UseDirectoryBrowser .
El código siguiente permite proporcionar archivos estáticos y el archivo predeterminado. El examen de
directorios no está habilitado.
app.UseFileServer();
app.UseFileServer(enableDirectoryBrowsing: true);
app.UseFileServer(new FileServerOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "MyStaticFiles")),
RequestPath = "/StaticFiles",
EnableDirectoryBrowsing = true
});
}
Al usar la jerarquía de archivos y el código anterior, las direcciones URL se resuelven como se indica a
continuación:
http://<dirección_servidor>/StaticFiles/images/banner1 MyStaticFiles/images/banner1.svg
.svg
http://<dirección_servidor>/StaticFiles MyStaticFiles/default.html
NOTE
UseDefaultFiles y UseDirectoryBrowser usan la dirección URL http://<dirección_servidor>/StaticFiles sin la
barra diagonal final para desencadenar un redireccionamiento del lado cliente a
http://<dirección_servidor>/StaticFiles/. Tenga en cuenta la adición de la barra diagonal final. Las direcciones URL
relativas dentro de los documentos se consideran no válidas sin una barra diagonal final.
FileExtensionContentTypeProvider
La clase FileExtensionContentTypeProvider contiene una propiedad Mappings que actúa como una
asignación de extensiones de archivo para tipos de contenido MIME. En el ejemplo siguiente, se registran
varias extensiones de archivo a los tipos MIME conocidos. Se reemplaza la extensión .rtf y se quita .mp4.
public void Configure(IApplicationBuilder app)
{
// Set up custom content types - associating file extension to MIME type
var provider = new FileExtensionContentTypeProvider();
// Add new mappings
provider.Mappings[".myapp"] = "application/x-msdownload";
provider.Mappings[".htm3"] = "text/html";
provider.Mappings[".image"] = "image/png";
// Replace an existing mapping
provider.Mappings[".rtf"] = "application/x-msdownload";
// Remove MP4 videos.
provider.Mappings.Remove(".mp4");
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "images")),
RequestPath = "/MyImages",
ContentTypeProvider = provider
});
app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "images")),
RequestPath = "/MyImages"
});
}
Con el código anterior, una solicitud para un archivo con un tipo de contenido desconocido se devuelve
como una imagen.
WARNING
Habilitar ServeUnknownFileTypes supone un riesgo para la seguridad. Está deshabilitado de forma predeterminada
y no se recomienda su uso. FileExtensionContentTypeProvider proporciona una alternativa más segura a ofrecer
archivos con extensiones no estándar.
Consideraciones
WARNING
UseDirectoryBrowser y UseStaticFiles pueden producir pérdidas de información confidencial. Se recomienda
deshabilitar el examen de directorios en producción. Revise cuidadosamente los directorios que se habilitan
mediante UseStaticFiles o UseDirectoryBrowser . Todo el directorio y sus subdirectorios pasan a ser accesibles
públicamente. Almacene los archivos adecuados para proporcionarlos al público en un directorio dedicado, como
<raíz_contenido>/wwwroot. Separe estos archivos de las vistas MVC, las páginas de Razor (solo 2.x), los archivos
de configuración, etc.
WARNING
Si el controlador de archivos estáticos de IIS está habilitado y el módulo de ASP.NET Core no está configurado
correctamente, se proporcionan archivos estáticos. Esto sucede, por ejemplo, si el archivo web.config no está
implementado.
Coloque los archivos de código (incluidos .cs y .cshtml) fuera de la raíz web del proyecto de la
aplicación. Por lo tanto, se crea una separación lógica entre el contenido del lado cliente de la aplicación
y el código basado en servidor. Esto impide que se filtre el código del lado servidor.
Recursos adicionales
Middleware
Introducción a ASP.NET Core
Enrutamiento en ASP.NET Core
18/06/2018 • 41 minutes to read • Edit Online
IMPORTANT
En este documento se describe el enrutamiento de nivel bajo de ASP.NET Core. Para más información sobre el
enrutamiento de ASP.NET Core MVC, vea Enrutamiento a las acciones de controlador.
VirtualPathContext.Values
VirtualPathContext.AmbientValues
Las rutas usan principalmente los valores de ruta proporcionados por Values y AmbientValues para decidir
dónde se puede generar una dirección URL y qué valores se deben incluir. AmbientValues son el conjunto
de valores de ruta producidos cuando la solicitud actual coincide con el sistema de enrutamiento. En
cambio, Values son los valores de ruta que especifican cómo se genera la dirección URL deseada para la
operación actual. Se proporciona HttpContext por si una ruta necesita obtener servicios o datos
adicionales asociados con el contexto actual.
Sugerencia: Considere Values como un conjunto de invalidaciones de AmbientValues . La generación de
dirección URL intenta reutilizar los valores de ruta de la solicitud actual para que sea más fácil generar
direcciones URL para los vínculos con la misma ruta o valores de ruta.
La salida de GetVirtualPath es VirtualPathData . VirtualPathData es un valor paralelo de RouteData ;
contiene el valor VirtualPath de la dirección URL de salida y algunas propiedades más que la ruta debe
establecer.
La propiedad VirtualPathData.VirtualPath contiene la ruta de acceso virtual generada por la ruta. Es
posible que deba procesar aún más la ruta de acceso, según sus necesidades. Por ejemplo, si quiere
representar la dirección URL generada en HTML debe anteponer la ruta de acceso base de la aplicación.
VirtualPathData.Router es una referencia a la ruta que ha generado correctamente la dirección URL.
La propiedad VirtualPathData.DataTokens es un diccionario de datos adicionales relacionados con la ruta
que ha generado la dirección URL. Se trata del valor paralelo de RouteData.DataTokens .
Creación de rutas
El enrutamiento proporciona la clase Route como implementación estándar de IRouter . Route usa la
sintaxis de plantilla de ruta para definir los patrones que se harán coincidir con la ruta de dirección URL
cuando se llame a RouteAsync . Route usará la misma plantilla de ruta para generar una dirección URL
cuando se llame a GetVirtualPath .
La mayoría de las aplicaciones creará rutas mediante una llamada a MapRoute o a uno de los métodos de
extensión similares definidos en IRouteBuilder . Todos estos métodos crearán una instancia de Route y la
agregarán a la colección de rutas.
Nota: MapRoute no toma ningún parámetro de controlador de ruta, solo agrega las rutas que procesará
DefaultHandler . Dado que el controlador predeterminado es IRouter , es posible que decida no controlar
la solicitud. Por ejemplo, ASP.NET MVC suele configurarse como un controlador predeterminado que solo
controla las solicitudes que coinciden con un controlador y una acción disponibles. Para más información
sobre el enrutamiento a MVC, vea Enrutamiento a las acciones de controlador.
Este es un ejemplo de una llamada a MapRoute usada por una definición de ruta típica de ASP.NET MVC:
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
Esta plantilla coincidirá con una ruta de dirección URL como /Products/Details/17 y extraerá los valores de
ruta { controller = Products, action = Details, id = 17 } . Los valores de ruta se determinan al dividir la
ruta de dirección URL en segmentos y hacer coincidir cada segmento con el nombre del parámetro de ruta
de la plantilla de ruta. Los parámetros de ruta tienen un nombre asignado. Para definirlos, el nombre del
parámetro se incluye entre llaves { } .
La plantilla anterior también podría coincidir con la ruta de dirección URL / y generaría los valores
{ controller = Home, action = Index } . Esto se debe a que los parámetros de ruta {controller} y
{action} tienen valores predeterminados y el parámetro de ruta id es opcional. Un signo igual =
seguido de un valor después del nombre del parámetro de ruta define un valor predeterminado para el
parámetro. Un signo de interrogación ? después del nombre del parámetro de ruta define el parámetro
como opcional. Los parámetros de ruta con un valor predeterminado siempre generan un valor de ruta
cuando la ruta coincide, mientras que los parámetros opcionales no generarán un valor de ruta si no hay
ningún segmento de ruta de dirección URL correspondiente.
Vea en Referencia de plantilla de ruta una descripción detallada de las características y la sintaxis de la
plantilla de ruta.
En este ejemplo se incluye una restricción de ruta:
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id:int}");
Esta plantilla coincidirá con una ruta de dirección URL como /Products/Details/17 , pero no con
/Products/Details/Apples . La definición de parámetro de ruta {id:int} define una restricción de ruta para
el parámetro de ruta id . Las restricciones de ruta implementan IRouteConstraint e inspeccionan los
valores de ruta para comprobarlos. En este ejemplo, el valor de ruta id debe poder convertirse en un
entero. Vea en Referencia de restricción de ruta una explicación más detallada de las restricciones de ruta
que se proporcionan con el marco de trabajo.
Las sobrecargas adicionales de MapRoute aceptan valores para constraints , dataTokens y defaults . Estos
parámetros adicionales de MapRoute se definen como un tipo object . Estos parámetros suelen usarse para
pasar un objeto de tipo anónimo, donde los nombres de propiedad del tipo anónimo coinciden con los
nombres de los parámetros de ruta.
En los dos ejemplos siguientes se crean rutas equivalentes:
routes.MapRoute(
name: "default_route",
template: "{controller}/{action}/{id?}",
defaults: new { controller = "Home", action = "Index" });
routes.MapRoute(
name: "default_route",
template: "{controller=Home}/{action=Index}/{id?}");
Sugerencia: La sintaxis insertada para definir las restricciones y los valores predeterminados puede ser más
conveniente para rutas simples, pero hay algunas características como los tokens de datos que no son
compatibles con esta sintaxis.
En este ejemplo se muestran otras características:
routes.MapRoute(
name: "blog",
template: "Blog/{*article}",
defaults: new { controller = "Blog", action = "ReadArticle" });
Esta plantilla coincidirá con una ruta de dirección URL como /Blog/All-About-Routing/Introduction y
extraerá los valores
{ controller = Blog, action = ReadArticle, article = All-About-Routing/Introduction } . La ruta genera los
valores de ruta predeterminados para controller y action , incluso si no hay parámetros de ruta
correspondientes en la plantilla. Los valores predeterminados pueden especificarse en la plantilla de ruta. El
parámetro de ruta article se define como un comodín por la aparición de un asterisco * antes del
nombre del parámetro de ruta. Los parámetros de ruta comodín capturan el resto de la ruta de dirección
URL y también pueden coincidir con la cadena vacía.
En este ejemplo se agregan restricciones de ruta y tokens de datos:
routes.MapRoute(
name: "us_english_products",
template: "en-US/Products/{id}",
defaults: new { controller = "Products", action = "Details" },
constraints: new { id = new IntRouteConstraint() },
dataTokens: new { locale = "en-US" });
Esta plantilla coincidirá con una ruta de dirección URL como /Products/5 y extraerá los valores
{ controller = Products, action = Details, id = 5 } y los tokens de datos { locale = en-US } .
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
Con los valores de ruta { controller = Products, action = List } , esta ruta generará la dirección URL
/Products/List . Los valores de ruta se sustituyen por los parámetros de ruta correspondientes para formar
la ruta de dirección URL. Dado que id es un parámetro de ruta opcional, no supone ningún problema que
no tenga un valor.
Con los valores de ruta { controller = Home, action = Index } , esta ruta generará la dirección URL / . Los
valores de ruta proporcionados coinciden con los valores predeterminados, por lo que los segmentos
correspondientes con esos valores se pueden omitir sin ningún riesgo. Tenga en cuenta que ambas
direcciones URL generadas harían un recorrido de ida y vuelta con esta definición de ruta y generarían los
mismos valores de ruta que se han usado para generar la dirección URL.
Sugerencia: Las aplicaciones con ASP.NET MVC deben usar UrlHelper para generar direcciones URL en
lugar de llamar al enrutamiento directamente.
Para obtener más detalles sobre el proceso de generación de dirección URL, vea Referencia de generación
de dirección URL.
Las rutas deben configurarse en el método Configure en la clase Startup . En el ejemplo siguiente se usan
estas API:
RouteBuilder
Build
MapGet coincide solamente con solicitudes HTTP GET
UseRouter
routeBuilder.MapRoute(
"Track Package Route",
"package/{operation:regex(^(track|create|detonate)$)}/{id:int}");
Si va a configurar una única ruta, pase una instancia de IRouter para llamar a app.UseRouter . No tendrá
que llamar a RouteBuilder .
El marco de trabajo proporciona un conjunto de métodos de extensión para crear rutas, como los
siguientes:
MapRoute
MapGet
MapPost
MapPut
MapDelete
MapVerb
Algunos de estos métodos, como MapGet , requieren que se proporcione un valor RequestDelegate . El valor
RequestDelegate se usará como controlador de ruta cuando la ruta coincida. Otros métodos de esta familia
permiten configurar una canalización de software intermedio que se usará como controlador de ruta. Si el
método Map no acepta un controlador, como MapRoute , usará DefaultHandler .
Los métodos Map[Verb] usan restricciones para limitar la ruta al verbo HTTP en el nombre del método. Por
ejemplo, vea MapGet y MapVerb.
Puede usar el carácter * como prefijo de un parámetro de ruta para enlazar con el resto del URI; es lo que
se denomina un parámetro comodín. Por ejemplo, blog/{*slug} coincidirá con cualquier URI que empiece
por /blog y que vaya seguido por cualquier valor (que se asignaría al valor de ruta slug ). Los parámetros
comodín también pueden coincidir con una cadena vacía.
Los parámetros de ruta pueden tener valores predeterminados. Para designar un valor predeterminado, se
especifica después del nombre de parámetro, separado por un signo = . Por ejemplo, {controller=Home}
definiría Home como el valor predeterminado de controller . El valor predeterminado se usa si no hay
ningún valor en la dirección URL para el parámetro. Además de los valores predeterminados, los
parámetros de ruta pueden ser opcionales (para especificarlos, se anexa un signo ? al final del nombre del
parámetro, como en id? ). La diferencia entre ser opcional y tener un valor predeterminado es que un
parámetro de ruta con un valor predeterminado siempre genera un valor, mientras que un parámetro
opcional tiene un valor solo cuando se proporciona uno.
Los parámetros de ruta también pueden tener restricciones, que deben coincidir con el valor de ruta
enlazado desde la dirección URL. Si se agrega un carácter de dos puntos : y un nombre de restricción
después del nombre del parámetro de ruta se especifica una restricción insertada en un parámetro de ruta.
Si la restricción requiere argumentos, se proporcionan entre paréntesis ( ) después del nombre de
restricción. Se pueden especificar varias restricciones insertadas si se anexa otro carácter de dos puntos :
y un nombre de restricción. El nombre de restricción se pasa al servicio IInlineConstraintResolver para
crear una instancia de IRouteConstraint para su uso en el procesamiento de direcciones URL. Por ejemplo,
la plantilla de ruta blog/{article:minlength(10)} especifica la restricción minlength con el argumento 10 .
Para obtener una descripción más detalladas de las restricciones de ruta y una lista de las restricciones
proporcionadas por el marco de trabajo, vea Referencia de restricción de ruta.
En la tabla siguiente se muestran algunas plantillas de ruta y su comportamiento.
El uso de una plantilla suele ser el método de enrutamiento más sencillo. Las restricciones y los valores
predeterminados también se pueden especificar fuera de la plantilla de ruta.
Sugerencia: Habilite el registro para ver de qué forma las implementaciones de enrutamiento integradas,
como Route , coinciden con las solicitudes.
WARNING
Evite el uso de restricciones para la validación de entradas, porque si lo hace, la entrada no válida producirá un
error "404 No encontrado" en lugar de un error 400 con el mensaje de error adecuado. Las restricciones de ruta
deben usarse para eliminar la ambigüedad entre rutas similares, no para validar las entradas de una ruta
determinada.
WARNING
Las restricciones de ruta que comprueban que la dirección URL se puede convertir en un tipo CLR (como int o
DateTime ) usan siempre la referencia cultural invariable, es decir, dan por supuesto que la dirección URL no es
localizable. Las restricciones de ruta proporcionadas por el marco de trabajo no modifican los valores almacenados en
los valores de ruta. Todos los valores de ruta analizados desde la dirección URL se almacenarán como cadenas. Por
ejemplo, la restricción de ruta Float intentará convertir el valor de ruta en un valor Float, pero el valor convertido se
usa exclusivamente para comprobar que se puede convertir en Float.
Expresiones regulares
El marco de trabajo de ASP.NET Core agrega
al constructor de
RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant
expresiones regulares. En RegexOptions Enumeration (Enumeración RegexOptions) puede ver una
descripción de estos miembros.
Las expresiones regulares usan delimitadores y tokens similares a los que usan el enrutamiento y el
lenguaje C#. Es necesario usar secuencias de escape con los tokens de expresiones regulares. Por ejemplo,
para usar la expresión regular ^\d{3}-\d{2}-\d{4}$ en el enrutamiento, es necesario escribir los caracteres
\ como \\ en el archivo de código fuente de C# para que el carácter de escape de cadena \ tenga una
secuencia de escape (a menos que use literales de cadena textuales. Es necesario incluir una secuencia de
escape en los caracteres { , } , "[" y "]". Para ello, duplíquelos a fin de incluir una secuencia de escape en
los caracteres delimitadores del parámetro de enrutamiento. En la tabla siguiente se muestra una expresión
regular y la versión con una secuencia de escape.
EXPRESIÓN NOTA
Las expresiones regulares usadas en el enrutamiento suelen empezar con el carácter ^ (coincidencia con
la posición inicial de la cadena) y acabar con el carácter $ (coincidencia con la posición final de la cadena).
Los caracteres ^ y $ garantizan que la expresión regular coincide con el valor completo del parámetro de
ruta. Sin los caracteres ^ y $ , la expresión regular coincidirá con cualquier subcadena de la cadena, lo que
a menudo no es deseable. En la tabla siguiente se muestran algunos ejemplos y se explica por qué que
coinciden o no.
Consulte las expresiones regulares de .NET Framework para obtener más información sobre la sintaxis de
expresiones regulares.
Para restringir un parámetro a un conjunto conocido de valores posibles, use una expresión regular. Por
ejemplo, {action:regex(^(list|get|create)$)} solo hace coincidir el valor de ruta action con list , get o
create . Si se pasa al diccionario de restricciones, la cadena "^(list|get|create)$" sería equivalente. Las
restricciones que se pasan al diccionario de restricciones (no insertado en una plantilla) que no coinciden
con una de las restricciones conocidas también se tratan como expresiones regulares.
Referencia de generación de dirección URL
En el ejemplo siguiente se muestra cómo se genera un vínculo a una ruta, dado un diccionario de valores
de ruta y un valor RouteCollection .
context.Response.ContentType = "text/html";
await context.Response.WriteAsync("Menu<hr/>");
await context.Response.WriteAsync($"<a href='{path}'>Create Package 123</a><br/>");
});
Si una ruta tiene un valor predeterminado que no se corresponde con un parámetro y ese valor se
proporciona explícitamente, debe coincidir con el valor predeterminado. Por ejemplo:
routes.MapRoute("blog_route", "blog/{*slug}",
defaults: new { controller = "Blog", action = "ReadPost" });
La generación de vínculos solo generará un vínculo para esta ruta si se proporcionan los valores
coincidentes para el controlador y la acción.
Middleware de reescritura de URL en ASP.NET Core
17/05/2018 • 31 minutes to read • Edit Online
NOTE
La reescritura de URL puede reducir el rendimiento de una aplicación. Cuando sea factible, debe limitar el número y la
complejidad de las reglas.
Package
Para incluir el middleware en el proyecto, agregue una referencia al paquete Microsoft.AspNetCore.Rewrite . Esta
característica está disponible para aplicaciones que tienen como destino ASP.NET Core 1.1 o posterior.
Extensión y opciones
Establezca las reglas de reescritura y redirección de URL mediante la creación de una instancia de la clase
RewriteOptions con métodos de extensión para cada una de las reglas. Encadene varias reglas en el orden que
quiera procesarlas. Las RewriteOptions se pasan al middleware de reescritura de URL cuando se agregan a la
canalización de solicitudes con app.UseRewriter(options); .
ASP.NET Core 2.x
ASP.NET Core 1.x
app.UseRewriter(options);
}
Redirección de URL
Use AddRedirect para redirigir las solicitudes. El primer parámetro contiene la expresión regular para hacer
coincidir la ruta de acceso de la URL entrante. El segundo parámetro es la cadena de reemplazo. El tercer
parámetro, si está presente, especifica el código de estado. Si no se especifica el código de estado, el valor
predeterminado es 302 (Encontrado), lo que indica que el recurso se ha movido o reemplazado temporalmente.
ASP.NET Core 2.x
ASP.NET Core 1.x
public void Configure(IApplicationBuilder app)
{
using (StreamReader apacheModRewriteStreamReader =
File.OpenText("ApacheModRewrite.txt"))
using (StreamReader iisUrlRewriteStreamReader =
File.OpenText("IISUrlRewrite.xml"))
{
var options = new RewriteOptions()
.AddRedirect("redirect-rule/(.*)", "redirected/$1")
.AddRewrite(@"^rewrite-rule/(\d+)/(\d+)", "rewritten?var1=$1&var2=$2",
skipRemainingRules: true)
.AddApacheModRewrite(apacheModRewriteStreamReader)
.AddIISUrlRewrite(iisUrlRewriteStreamReader)
.Add(MethodRules.RedirectXMLRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
En un explorador con herramientas de desarrollo habilitadas, realice una solicitud a la aplicación de ejemplo con
la ruta de acceso /redirect-rule/1234/5678 . La expresión regular coincide con la ruta de acceso de la solicitud en
redirect-rule/(.*) y la ruta de acceso se reemplaza con /redirected/1234/5678 . La URL de redireccionamiento
se devuelve al cliente con un código de estado 302 (Encontrado). El explorador realiza una solicitud nueva en la
URL de redireccionamiento, que aparece en la barra de direcciones del explorador. Puesto que no hay ninguna
regla en la aplicación de ejemplo que coincida con la URL de redireccionamiento, la segunda solicitud recibe una
respuesta 200 (Correcto) de la aplicación y el cuerpo de la respuesta muestra la URL de redireccionamiento. Se
realiza un recorrido de ida y vuelta al servidor cuando se redirige una URL.
WARNING
Tenga cuidado al establecer las reglas de redirección. Las reglas de redirección se evalúan en cada solicitud enviada a la
aplicación, incluso después de una redirección. Es fácil crear por error un bucle de redirecciones infinitas.
La parte de la expresión incluida entre paréntesis se denomina grupo de capturas. El punto ( . ) de la expresión
significa buscar cualquier carácter. El asterisco ( * ) indica buscar el carácter precedente cero o más veces. Por
tanto, el grupo de capturas (.*) busca los dos últimos segmentos de la ruta de acceso de la URL, 1234/5678 .
Cualquier valor que se proporcione en la URL de la solicitud después de redirect-rule/ es capturado por este
grupo de capturas único.
En la cadena de reemplazo, se insertan los grupos capturados en la cadena con el signo de dólar ( $ ) seguido del
número de secuencia de la captura. Se obtiene el valor del primer grupo de capturas con $1 , el segundo con $2
, y así siguen en secuencia para los grupos de capturas de la expresión regular. Solo hay un grupo capturado en la
expresión regular de la regla de redirección de la aplicación de ejemplo, por lo que solo hay un grupo insertado
en la cadena de reemplazo, que es $1 . Cuando se aplica la regla, la URL se convierte en /redirected/1234/5678 .
Redirección de URL a un punto de conexión segura
Use AddRedirectToHttps para redirigir solicitudes HTTP al mismo host y ruta con HTTPS ( https:// ). Si no se
proporciona el código de estado, el middleware muestra de forma predeterminada 302 (Encontrado). Si no se
proporciona el puerto, el middleware muestra de forma predeterminada null , lo que significa que el protocolo
cambia a https:// y el cliente accede al recurso en el puerto 443. En el ejemplo se muestra cómo establecer el
código de estado en 301 (Movido definitivamente) y cambiar el puerto a 5001.
app.UseRewriter(options);
}
Use AddRedirectToHttpsPermanent para redirigir las solicitudes poco seguras al mismo host y ruta de acceso
mediante el protocolo HTTPS seguro ( https:// en el puerto 443). El middleware establece el código de estado
en 301 (Movido definitivamente).
app.UseRewriter(options);
}
app.UseRewriter(options);
}
/redirect-rule/1234/5678 Sí
/my-cool-redirect-rule/1234/5678 Sí
/anotherredirect-rule/1234/5678 Sí
La regla de reescritura, ^rewrite-rule/(\d+)/(\d+) , solo encuentra rutas de acceso que empiezan con
rewrite-rule/ . Observe la diferencia de coincidencia entre la siguiente regla de reescritura y la regla de
redirección anterior.
/rewrite-rule/1234/5678 Sí
/my-cool-rewrite-rule/1234/5678 No
/anotherrewrite-rule/1234/5678 No
Después de la parte ^rewrite-rule/ de la expresión, hay dos grupos de captura, (\d+)/(\d+) . \d significa
buscar un dígito (número ). El signo más ( + ) significa buscar uno o más de los caracteres anteriores. Por tanto, la
URL debe contener un número seguido de una barra diagonal, seguida de otro número. Estos grupos de
capturas se insertan en la URL de reescritura como $1 y $2 . La cadena de reemplazo de la regla de reescritura
coloca los grupos capturados en la cadena de consulta. La ruta de acceso solicitada de /rewrite-rule/1234/5678
se reescribe para obtener el recurso en /rewritten?var1=1234&var2=5678 . Si una cadena de consulta está presente
en la solicitud original, se conserva cuando se reescribe la URL.
No hay ningún recorrido de ida y vuelta al servidor para obtener el recurso. Si el recurso existe, se captura y se
devuelve al cliente con un código de estado 200 (Correcto). Como el cliente no se redirige, la URL no cambia en
la barra de direcciones del explorador. En lo que al cliente se refiere, la operación de reescritura de URL nunca se
produjo.
NOTE
Use skipRemainingRules: true siempre que sea posible, ya que las reglas de coincidencia son un proceso costoso y
reducen el tiempo de respuesta de aplicación. Para obtener la respuesta más rápida de la aplicación:
Ordene las reglas de reescritura desde la que coincida con más frecuencia a la que coincida con menos frecuencia.
Omita el procesamiento de las reglas restantes cuando se produzca una coincidencia; no es necesario ningún
procesamiento de reglas adicional.
mod_rewrite de Apache
Aplique reglas mod_rewrite de Apache con AddApacheModRewrite . Asegúrese de que el archivo de reglas se
implementa con la aplicación. Para obtener más información y ejemplos de reglas mod_rewrite, vea Apache
mod_rewrite (mod_rewrite de Apache).
ASP.NET Core 2.x
ASP.NET Core 1.x
Se usa StreamReader para leer las reglas del archivo de reglas ApacheModRewrite.txt.
app.UseRewriter(options);
}
app.UseRewriter(options);
}
<rewrite>
<rules>
<rule name="Rewrite segment to id querystring" stopProcessing="true">
<match url="^iis-rules-rewrite/(.*)$" />
<action type="Rewrite" url="rewritten?id={R:1}" appendQueryString="false"/>
</rule>
</rules>
</rewrite>
NOTE
También puede obtener IFileProvider a través de PhysicalFileProvider . Con este enfoque logrará mayor flexibilidad
para la ubicación de los archivos de reglas de reescritura. Asegúrese de que los archivos de reglas de reescritura se
implementan en el servidor en la ruta de acceso que proporcione.
app.UseRewriter(options);
}
La aplicación de ejemplo muestra un método que redirige las solicitudes para las rutas de acceso que terminen
con .xml. Si realiza una solicitud para /file.xml , se redirige a /xmlfiles/file.xml . El código de estado se
establece en 301 (Movido definitivamente). Para una redirección, debe establecer explícitamente el código de
estado de la respuesta; en caso contrario, se devuelve un código de estado 200 (Correcto) y no se produce la
redirección en el cliente.
public static void RedirectXMLRequests(RewriteContext context)
{
var request = context.HttpContext.Request;
if (request.Path.Value.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
{
var response = context.HttpContext.Response;
response.StatusCode = StatusCodes.Status301MovedPermanently;
context.Result = RuleResult.EndResponse;
response.Headers[HeaderNames.Location] =
"/xmlfiles" + request.Path + request.QueryString;
}
}
app.UseRewriter(options);
}
Se comprueba que los valores de los parámetros en la aplicación de ejemplo para extension y newPath
cumplen ciertas condiciones. extension debe contener un valor, que debe ser .png, .jpg o .gif. Si newPath no es
válido, se genera ArgumentException . Si se realiza una solicitud para image.png, se redirige a
/png-images/image.png . Si se realiza una solicitud para image.jpg, se redirige a /jpg-images/image.jpg . El código
de estado se establece en 301 (Movido definitivamente) y se establece context.Result para detener el
procesamiento de reglas y enviar la respuesta.
public class RedirectImageRequests : IRule
{
private readonly string _extension;
private readonly PathString _newPath;
if (!Regex.IsMatch(extension, @"^\.(png|jpg|gif)$"))
{
throw new ArgumentException("Invalid extension", nameof(extension));
}
if (!Regex.IsMatch(newPath, @"(/[A-Za-z0-9]+)+?"))
{
throw new ArgumentException("Invalid path", nameof(newPath));
}
_extension = extension;
_newPath = new PathString(newPath);
}
if (request.Path.Value.EndsWith(_extension, StringComparison.OrdinalIgnoreCase))
{
var response = context.HttpContext.Response;
response.StatusCode = StatusCodes.Status301MovedPermanently;
context.Result = RuleResult.EndResponse;
response.Headers[HeaderNames.Location] =
_newPath + request.Path + request.QueryString;
}
}
}
Recursos adicionales
Inicio de aplicaciones
Middleware
Expresiones regulares en .NET
Lenguaje de expresiones regulares: referencia rápida
Apache mod_rewrite (mod_rewrite de Apache)
Using Url Rewrite Module 2.0 (for IIS ) (Uso del Módulo URL Rewrite 2.0 para IIS )
URL Rewrite Module Configuration Reference (Referencia de configuración del Módulo URL Rewrite)
IIS URL Rewrite Module Forum (Foro del Módulo URL Rewrite para IIS )
Cómo simplificar la estructura de direcciones URL
10 URL Rewriting Tips and Tricks (10 trucos y consejos para reescritura de URL )
To slash or not to slash (Usar la barra diagonal o no)
Usar varios entornos en ASP.NET Core
19/06/2018 • 9 minutes to read • Edit Online
Entornos
ASP.NET Core lee la variable de entorno ASPNETCORE_ENVIRONMENT al inicio de la aplicación y almacena ese
valor en IHostingEnvironment.EnvironmentName. ASPNETCORE_ENVIRONMENT se puede establecer en cualquier
valor, pero el marco de trabajo admite tres valores: Development, Staging y Production. Si no se ha
establecido ASPNETCORE_ENVIRONMENT , el valor predeterminado será Production .
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
El código anterior:
Llama a UseDeveloperExceptionPage y UseBrowserLink cuando ASPNETCORE_ENVIRONMENT está
establecido en Development .
Llama a UseExceptionHandler cuando el valor de ASPNETCORE_ENVIRONMENT está establecido en uno de
los siguientes:
Staging
Production
Staging_2
<environment include="Development">
<div><environment include="Development"></div>
</environment>
<environment exclude="Development">
<div><environment exclude="Development"></div>
</environment>
<environment include="Staging,Development,Staging_2">
<div>
<environment include="Staging,Development,Staging_2">
</div>
</environment>
Nota: En Windows y macOS, los valores y las variables de entorno no distinguen mayúsculas de minúsculas.
Los valores y las variables de entorno de Linux distinguen mayúsculas de minúsculas de forma
predeterminada.
Desarrollo
El entorno de desarrollo puede habilitar características que no deben exponerse en producción. Por ejemplo,
las plantillas de ASP.NET Core habilitan la página de excepciones para el desarrollador en el entorno de
desarrollo.
El entorno para el desarrollo del equipo local se puede establecer en el archivo Properties\launchSettings.json
del proyecto. Los valores de entorno establecidos en launchSettings.json invalidan los valores establecidos en
el entorno del sistema.
En el siguiente fragmento de JSON se muestran tres perfiles de un archivo launchSettings.json:
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:54339/",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"WebApp1": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Staging"
},
"applicationUrl": "http://localhost:54340/"
},
"Kestrel Staging": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_My_Environment": "1",
"ASPNETCORE_DETAILEDERRORS": "1",
"ASPNETCORE_ENVIRONMENT": "Staging"
},
"applicationUrl": "http://localhost:51997/"
}
}
}
NOTE
La propiedad applicationUrl en launchSettings.json puede especificar una lista de direcciones URL del servidor. Use
un punto y coma entre las direcciones URL de la lista:
"WebApplication1": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
Cuando la aplicación se inicia con dotnet run, se usará el primer perfil con "commandName": "Project" . El valor
de commandName especifica el servidor web que se va a iniciar. commandName puede ser una de las siguientes
opciones:
IIS Express
IIS
Project (que inicia Kestrel)
Cuando una aplicación se inicia con dotnet run:
Se lee launchSettings.json, si está disponible. La configuración de environmentVariables de
launchSettings.json reemplaza las variables de entorno.
Se muestra el entorno de hospedaje.
En la siguiente salida se muestra una aplicación que se ha iniciado con dotnet run:
La pestaña Depurar de Visual Studio proporciona una GUI para editar el archivo launchSettings.json:
Los cambios realizados en los perfiles de proyecto podrían no surtir efecto hasta que se reinicie el servidor
web. Es necesario reiniciar Kestrel para que detecte los cambios realizados en su entorno.
WARNING
En launchSettings.json no se deben almacenar secretos. Se puede usar la herramienta Administrador de secretos a fin
de almacenar secretos para el desarrollo local.
Producción
El entorno de producción debe configurarse para maximizar la seguridad, el rendimiento y la solidez de la
aplicación. Las opciones de configuración comunes que difieren de las del entorno de desarrollo son:
Almacenamiento en caché.
Los recursos del cliente se agrupan, se reducen y se atienden potencialmente desde una CDN.
Las páginas de error de diagnóstico están deshabilitadas.
Las páginas de error descriptivas están habilitadas.
El registro y la supervisión de la producción están habilitados. Por ejemplo, Application Insights.
set ASPNETCORE_ENVIRONMENT=Development
PowerShell
$Env:ASPNETCORE_ENVIRONMENT = "Development"
Estos comandos solo tienen efecto en la ventana actual. Cuando se cierre la ventana, la configuración de
ASPNETCORE_ENVIRONMENT volverá a la configuración predeterminada o al valor del equipo. Para
establecer el valor globalmente en Windows, abra Panel de Control > Sistema > Configuración
avanzada del sistema y agregue o edite el valor ASPNETCORE_ENVIRONMENT .
web.config
Vea la sección Establecer variables de entorno del tema Referencia de configuración del módulo ASP.NET
Core.
Por grupo de aplicaciones de IIS
Para establecer variables de entorno para aplicaciones individuales que se ejecutan en grupos de aplicaciones
aislados (se admite en IIS 10.0+), vea la sección AppCmd.exe command (Comando AppCmd.exe) del tema
Environment Variables <environmentVariables> (Variables de entorno ).
macOS
Para establecer el entorno actual para macOS, puede hacerlo en línea al ejecutar la aplicación.
export ASPNETCORE_ENVIRONMENT=Development
Las variables de entorno de nivel de equipo se establecen en el archivo .bashrc o .bash_profile. Para editar el
archivo, use cualquier editor de texto y agregue la instrucción siguiente.
export ASPNETCORE_ENVIRONMENT=Development
Linux
Para distribuciones de Linux, use el comando export en la línea de comandos para la configuración de
variables basada en sesión y el archivo bash_profile para la configuración del entorno en el nivel de equipo.
Configuración de entorno
Para más información, vea Configuración de entorno.
if (env.IsProduction() || env.IsStaging())
{
throw new Exception("Not development.");
}
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
}
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
app.UseExceptionHandler("/Error");
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
}
Recursos adicionales
Inicio de aplicaciones
Configuración
IHostingEnvironment.EnvironmentName
Configuración en ASP.NET Core
21/06/2018 • 27 minutes to read • Edit Online
Por Rick Anderson, Mark Michaelis, Steve Smith, Daniel Roth y Luke Latham
La API de configuración proporciona una manera de configurar una aplicación web ASP.NET Core según una lista
de pares de nombre y valor. La configuración se lee en tiempo de ejecución desde varios orígenes. Puede agrupar
estos pares nombre-valor en una jerarquía multinivel.
Existen proveedores de configuración para:
Formatos de archivo (INI, JSON y XML ).
Argumentos de la línea de comandos.
Variables de entorno.
Objetos de .NET en memoria.
El almacenamiento de administrador secreto sin cifrar.
Un almacén de usuario cifrado, como Azure Key Vault.
Proveedores personalizados (instalados o creados).
Cada valor de configuración se asigna a una clave de cadena. Hay compatibilidad de enlace integrada para
deserializar la configuración en un objeto POCO personalizado (una clase simple de .NET con propiedades).
El patrón de opciones usa las clases de opciones para representar grupos de configuraciones relacionadas. Para
más información sobre cómo usar el patrón de opciones, vea el tema Opciones.
Vea o descargue el código de ejemplo (cómo descargarlo)
Configuración de JSON
En la aplicación de consola siguiente se usa el proveedor de configuración de JSON:
using System;
using System.IO;
// Requires NuGet package
// Microsoft.Extensions.Configuration.Json
using Microsoft.Extensions.Configuration;
Configuration = builder.Build();
Console.WriteLine($"option1 = {Configuration["Option1"]}");
Console.WriteLine($"option2 = {Configuration["option2"]}");
Console.WriteLine(
$"suboption1 = {Configuration["subsection:suboption1"]}");
Console.WriteLine();
Console.WriteLine("Wizards:");
Console.Write($"{Configuration["wizards:0:Name"]}, ");
Console.WriteLine($"age {Configuration["wizards:0:Age"]}");
Console.Write($"{Configuration["wizards:1:Name"]}, ");
Console.WriteLine($"age {Configuration["wizards:1:Age"]}");
Console.WriteLine();
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
{
"option1": "value1_from_json",
"option2": 2,
"subsection": {
"suboption1": "subvalue1_from_json"
},
"wizards": [
{
"Name": "Gandalf",
"Age": "1000"
},
{
"Name": "Harry",
"Age": "17"
}
]
}
La configuración consta de una lista jerárquica de pares de nombre y valor en los que los nodos se separan con dos
puntos ( : ). Para recuperar un valor, obtenga acceso al indizador Configuration con la clave del elemento
correspondiente:
Console.WriteLine(
$"suboption1 = {Configuration["subsection:suboption1"]}");
Para trabajar con matrices en orígenes de configuración con formato JSON, use un índice de matriz como parte de
la cadena separada por dos puntos. En el ejemplo siguiente se obtiene el nombre del primer elemento de la matriz
wizards anterior:
Console.Write($"{Configuration["wizards:0:Name"]}");
// Output: Gandalf
Los pares de nombre y valor que se escriben en los proveedores de Configuración integrados no se conservan,
pero se puede crear un proveedor personalizado que guarde los valores. Vea Proveedor de configuración
personalizado.
En el ejemplo anterior se usa el indizador de configuración para leer los valores. Para obtener acceso a la
configuración fuera de Startup , use el patrón de opciones. Para más información, vea el tema Opciones.
Configuración XML
Para trabajar con matrices en orígenes de configuración con formato XML, proporcione un índice name a cada
elemento. Use el índice para acceder a los valores:
<wizards>
<wizard name="Gandalf">
<age>1000</age>
</wizard>
<wizard name="Harry">
<age>17</age>
</wizard>
</wizards>
Console.Write($"{Configuration["wizard:Harry:age"]}");
// Output: 17
Configuración de entorno
Es habitual tener distintos valores de configuración para entornos diferentes, por ejemplo para desarrollo, pruebas
y producción. El método de extensión CreateDefaultBuilder en una aplicación de ASP.NET Core 2.x (o mediante
AddJsonFile y AddEnvironmentVariables directamente en una aplicación de ASP.NET Core 1.x) agrega proveedores
de configuración para leer archivos JSON y orígenes de configuración del sistema:
appsettings.json
appsettings.<NombreDelEntorno>.json
Variables de entorno
Las aplicaciones ASP.NET Core 1.x deben llamar a AddJsonFile y AddEnvironmentVariables.
Vea AddJsonFile para obtener una explicación de los parámetros. reloadOnChange solo se admite en ASP.NET Core
1.1 y versiones posteriores.
Los orígenes de configuración se leen en el orden en que se especifican. En el código anterior, las variables de
entorno se leen en último lugar. Cualquier valor de configuración establecido mediante el entorno reemplaza a los
establecidos en los dos proveedores anteriores.
Tenga en cuenta el siguiente archivo appsettings.Staging.json:
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"System": "Information",
"Microsoft": "Information"
}
},
"MyConfig": "My Config Value for staging."
}
if (env.IsProduction() || env.IsStaging())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
El entorno se establece normalmente en Development , Staging o Production . Para obtener más información,
consulte Uso de varios entornos.
Consideraciones de configuración:
IOptionsSnapshot puede volver a cargar los datos de configuración cuando cambia.
En las claves de configuraciones no se distingue entre mayúsculas y minúsculas.
Nunca almacene contraseñas u otros datos confidenciales en el código del proveedor de configuración o en
archivos de configuración de texto sin formato. No use secretos de producción en los entornos de desarrollo o
pruebas. Especifique los secretos fuera del proyecto para que no se confirmen en un repositorio de código
fuente de manera accidental. Obtenga más información sobre cómo usar con varios entornos y administrar el
almacenamiento seguro de los secretos de aplicación en el desarrollo.
Para los valores de configuración jerárquica especificados en las variables de entorno, el signo de dos puntos (
: ) podría no funcionar en todas las plataformas. El guion bajo doble ( __ ) es compatible con todas las
plataformas.
Al interactuar con la API de configuración, el signo de dos puntos ( : ) funciona en todas las plataformas.
Configuration = builder.Build();
Console.WriteLine($"Hello {Configuration["Profile:MachineName"]}");
Los valores de configuración se devuelven como cadenas, pero el enlace permite la construcción de objetos. El
enlace permite recuperar objetos POCO o incluso gráficos de objetos completos.
GetValue
En el ejemplo siguiente se muestra el método de extensión GetValue<T>:
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
Configuration = builder.Build();
Console.WriteLine($"Hello {Configuration["Profile:MachineName"]}");
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
using System;
using System.IO;
using Microsoft.Extensions.Configuration;
Console.WriteLine($"Height {appConfig.Window.Height}");
Console.WriteLine();
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
En ASP.NET Core 1.1 y versiones posteriores se puede usar Get<T> , que funciona con secciones completas.
Get<T> puede ser más conveniente que usar Bind . En el código siguiente se muestra cómo usar Get<T> con el
ejemplo anterior:
[Fact]
public void CanBindObjectTree()
{
var dict = new Dictionary<string, string>
{
{"App:Profile:Machine", "Rick"},
{"App:Connection:Value", "connectionstring"},
{"App:Window:Height", "11"},
{"App:Window:Width", "11"}
};
var builder = new ConfigurationBuilder();
builder.AddInMemoryCollection(dict);
var config = builder.Build();
Assert.Equal("Rick", settings.Profile.Machine);
Assert.Equal(11, settings.Window.Height);
Assert.Equal(11, settings.Window.Width);
Assert.Equal("connectionstring", settings.Connection.Value);
}
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
namespace CustomConfigurationProvider
{
public class EFConfigSource : IConfigurationSource
{
private readonly Action<DbContextOptionsBuilder> _optionsAction;
namespace CustomConfigurationProvider
{
public class EFConfigProvider : ConfigurationProvider
{
public EFConfigProvider(Action<DbContextOptionsBuilder> optionsAction)
{
OptionsAction = optionsAction;
}
Cuando se ejecuta el ejemplo, se muestran los valores resaltados de la base de datos ("value_from_ef_1" y
"value_from_ef_2").
Se puede usar un método de extensión EFConfigSource para agregar el origen de configuración:
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
namespace CustomConfigurationProvider
{
public static class EntityFrameworkExtensions
{
public static IConfigurationBuilder AddEntityFrameworkConfig(
this IConfigurationBuilder builder, Action<DbContextOptionsBuilder> setup)
{
return builder.Add(new EFConfigSource(setup));
}
}
}
using System;
using System.IO;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using CustomConfigurationProvider;
Console.WriteLine("key1={0}", config["key1"]);
Console.WriteLine("key2={0}", config["key2"]);
Console.WriteLine("key3={0}", config["key3"]);
Console.WriteLine();
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
Tenga en cuenta que el ejemplo agrega el EFConfigProvider personalizado después del proveedor de JSON, por lo
que cualquier configuración de la base de datos invalidará la configuración del archivo appsettings.json.
Con el siguiente archivo appsettings.json:
{
"ConnectionStrings": {
"DefaultConnection": "Server=
(localdb)\\mssqllocaldb;Database=CustomConfigurationProvider;Trusted_Connection=True;MultipleActiveResultSets=t
rue"
},
"key1": "value_from_json_1",
"key2": "value_from_json_2",
"key3": "value_from_json_3"
}
key1=value_from_ef_1
key2=value_from_ef_2
key3=value_from_json_3
builder.AddInMemoryCollection(dict)
.AddCommandLine(args);
Configuration = builder.Build();
Console.WriteLine($"MachineName: {Configuration["Profile:MachineName"]}");
Console.WriteLine($"Left: {Configuration["App:MainWindow:Left"]}");
Console.WriteLine();
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
MachineName: MairaPC
Left: 1980
Pasar pares de clave y valor de argumento en la línea de comandos cambia los valores de Profile:MachineName y
App:MainWindow:Left :
MachineName: BartPC
Left: 1979
Para invalidar la configuración proporcionada por otros proveedores de configuración con la configuración de línea
de comandos, llame a AddCommandLine en último lugar en ConfigurationBuilder :
Un solo argumento
El valor debe seguir a un signo igual ( = ). El valor puede ser NULL (por ejemplo, mykey= ).
La clave puede tener un prefijo.
†En las asignaciones de modificador que se describen a continuación debe proporcionarse una clave con un prefijo
de un solo guion ( - ).
Comando de ejemplo:
Nota: Si -key2 no está presente en las asignaciones de modificador que se proporcionan al proveedor de
configuración, se produce una excepción FormatException .
Secuencia de dos argumentos
El valor no puede ser NULL y debe seguir a la clave separado por un espacio.
La clave debe tener un prefijo.
†En las asignaciones de modificador que se describen a continuación debe proporcionarse una clave con un prefijo
de un solo guion ( - ).
Comando de ejemplo:
Nota: Si -key1 no está presente en las asignaciones de modificador que se proporcionan al proveedor de
configuración, se produce una excepción FormatException .
Claves duplicadas
Si se proporcionan claves duplicadas, se usa el último par de clave y valor.
Asignaciones de modificador
Si realiza la configuración de compilación manualmente con ConfigurationBuilder , se puede agregar un
diccionario de asignaciones de modificador al método AddCommandLine . Las asignaciones de modificador admiten la
lógica de sustitución de nombres de clave.
Cuando se usa el diccionario de asignaciones de modificador, se comprueba en el diccionario si una clave coincide
con la clave proporcionada por un argumento de línea de comandos. Si la clave de la línea de comandos se
encuentra en el diccionario, se devuelve el valor del diccionario (el reemplazo de la clave) para establecer la
configuración. Se requiere una asignación de conmutador para cualquier clave de línea de comandos precedida por
un solo guion ( - ).
Reglas de clave del diccionario de asignaciones de modificador:
Los modificadores deben empezar por un guion ( - ) o guion doble ( -- ).
El diccionario de asignaciones de modificador no debe contener claves duplicadas.
En el ejemplo siguiente, el método GetSwitchMappings permite que los argumentos de línea de comandos usen un
prefijo de clave de un solo guión ( - ) y evita prefijos de subclave iniciales.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Configuration;
builder.AddInMemoryCollection(dict)
.AddCommandLine(args, GetSwitchMappings(dict));
Configuration = builder.Build();
Console.WriteLine($"MachineName: {Configuration["Profile:MachineName"]}");
Console.WriteLine($"Left: {Configuration["App:MainWindow:Left"]}");
Console.WriteLine();
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
dotnet run
MachineName: RickPC
Left: 1980
Después de crear el diccionario de asignaciones de modificador, contiene los datos que se muestran en la tabla
siguiente:
KEY VALOR
-MachineName Profile:MachineName
-Left App:MainWindow:Left
Se intercambian las claves de la línea de comandos. En la ventana de consola se muestran los valores de
configuración de Profile:MachineName y App:MainWindow:Left :
MachineName: ChadPC
Left: 1988
Archivo web.config
Un archivo web.config es necesario cuando la aplicación se hospeda en IIS o IIS Express. La configuración de
web.config habilita el módulo ASP.NET Core para que inicie la aplicación y configure otros módulos y valores de
configuración de IIS. Si el archivo web.config no está presente y el archivo de proyecto incluye
<Project Sdk="Microsoft.NET.Sdk.Web"> , al publicar el proyecto se crea un archivo web.config en la salida publicada
(la carpeta de publicación). Para más información, vea Host ASP.NET Core on Windows with IIS (Hospedar
ASP.NET Core en Windows con IIS ).
@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration
<!DOCTYPE html>
<html lang="en">
<head>
<title>Index Page</title>
</head>
<body>
<h1>Access configuration in a Razor Pages page</h1>
<p>Configuration["key"]: @Configuration["key"]</p>
</body>
</html>
@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration
<!DOCTYPE html>
<html lang="en">
<head>
<title>Index View</title>
</head>
<body>
<h1>Access configuration in an MVC view</h1>
<p>Configuration["key"]: @Configuration["key"]</p>
</body>
</html>
Notas adicionales
La inserción de dependencias (DI) no se establece hasta que se invoca ConfigureServices .
El sistema de configuración no es compatible con DI.
IConfiguration tiene dos especializaciones:
IConfigurationRoot Se usa para el nodo raíz. Puede desencadenar una recarga.
IConfigurationSection Representa una sección de valores de configuración. Los métodos GetSection y
GetChildren devuelven un elemento IConfigurationSection .
Use IConfigurationRoot al recargar la configuración o para acceder a todos los proveedores. Ninguna de
estas situaciones son comunes.
Recursos adicionales
Opciones
Uso de varios entornos
Almacenamiento seguro de secretos de aplicación en el desarrollo
Hospedaje en ASP.NET Core
Inserción de dependencias
Proveedor de configuración de Azure Key Vault
Configuración en ASP.NET Core
21/06/2018 • 27 minutes to read • Edit Online
Por Rick Anderson, Mark Michaelis, Steve Smith, Daniel Roth y Luke Latham
La API de configuración proporciona una manera de configurar una aplicación web ASP.NET Core
según una lista de pares de nombre y valor. La configuración se lee en tiempo de ejecución desde varios
orígenes. Puede agrupar estos pares nombre-valor en una jerarquía multinivel.
Existen proveedores de configuración para:
Formatos de archivo (INI, JSON y XML ).
Argumentos de la línea de comandos.
Variables de entorno.
Objetos de .NET en memoria.
El almacenamiento de administrador secreto sin cifrar.
Un almacén de usuario cifrado, como Azure Key Vault.
Proveedores personalizados (instalados o creados).
Cada valor de configuración se asigna a una clave de cadena. Hay compatibilidad de enlace integrada
para deserializar la configuración en un objeto POCO personalizado (una clase simple de .NET con
propiedades).
El patrón de opciones usa las clases de opciones para representar grupos de configuraciones
relacionadas. Para más información sobre cómo usar el patrón de opciones, vea el tema Opciones.
Vea o descargue el código de ejemplo (cómo descargarlo)
Configuración de JSON
En la aplicación de consola siguiente se usa el proveedor de configuración de JSON:
using System;
using System.IO;
// Requires NuGet package
// Microsoft.Extensions.Configuration.Json
using Microsoft.Extensions.Configuration;
Configuration = builder.Build();
Console.WriteLine($"option1 = {Configuration["Option1"]}");
Console.WriteLine($"option2 = {Configuration["option2"]}");
Console.WriteLine(
$"suboption1 = {Configuration["subsection:suboption1"]}");
Console.WriteLine();
Console.WriteLine("Wizards:");
Console.Write($"{Configuration["wizards:0:Name"]}, ");
Console.WriteLine($"age {Configuration["wizards:0:Age"]}");
Console.Write($"{Configuration["wizards:1:Name"]}, ");
Console.WriteLine($"age {Configuration["wizards:1:Age"]}");
Console.WriteLine();
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
{
"option1": "value1_from_json",
"option2": 2,
"subsection": {
"suboption1": "subvalue1_from_json"
},
"wizards": [
{
"Name": "Gandalf",
"Age": "1000"
},
{
"Name": "Harry",
"Age": "17"
}
]
}
La configuración consta de una lista jerárquica de pares de nombre y valor en los que los nodos se
separan con dos puntos ( : ). Para recuperar un valor, obtenga acceso al indizador Configuration con la
clave del elemento correspondiente:
Console.WriteLine(
$"suboption1 = {Configuration["subsection:suboption1"]}");
Para trabajar con matrices en orígenes de configuración con formato JSON, use un índice de matriz
como parte de la cadena separada por dos puntos. En el ejemplo siguiente se obtiene el nombre del
primer elemento de la matriz wizards anterior:
Console.Write($"{Configuration["wizards:0:Name"]}");
// Output: Gandalf
Los pares de nombre y valor que se escriben en los proveedores de Configuración integrados no se
conservan, pero se puede crear un proveedor personalizado que guarde los valores. Vea Proveedor de
configuración personalizado.
En el ejemplo anterior se usa el indizador de configuración para leer los valores. Para obtener acceso a
la configuración fuera de Startup , use el patrón de opciones. Para más información, vea el tema
Opciones.
Configuración XML
Para trabajar con matrices en orígenes de configuración con formato XML, proporcione un índice name
a cada elemento. Use el índice para acceder a los valores:
<wizards>
<wizard name="Gandalf">
<age>1000</age>
</wizard>
<wizard name="Harry">
<age>17</age>
</wizard>
</wizards>
Console.Write($"{Configuration["wizard:Harry:age"]}");
// Output: 17
Configuración de entorno
Es habitual tener distintos valores de configuración para entornos diferentes, por ejemplo para
desarrollo, pruebas y producción. El método de extensión CreateDefaultBuilder en una aplicación de
ASP.NET Core 2.x (o mediante AddJsonFile y AddEnvironmentVariables directamente en una aplicación
de ASP.NET Core 1.x) agrega proveedores de configuración para leer archivos JSON y orígenes de
configuración del sistema:
appsettings.json
appsettings.<NombreDelEntorno>.json
Variables de entorno
Las aplicaciones ASP.NET Core 1.x deben llamar a AddJsonFile y AddEnvironmentVariables.
Vea AddJsonFile para obtener una explicación de los parámetros. reloadOnChange solo se admite en
ASP.NET Core 1.1 y versiones posteriores.
Los orígenes de configuración se leen en el orden en que se especifican. En el código anterior, las
variables de entorno se leen en último lugar. Cualquier valor de configuración establecido mediante el
entorno reemplaza a los establecidos en los dos proveedores anteriores.
Tenga en cuenta el siguiente archivo appsettings.Staging.json:
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"System": "Information",
"Microsoft": "Information"
}
},
"MyConfig": "My Config Value for staging."
}
if (env.IsProduction() || env.IsStaging())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
Configuration = builder.Build();
Console.WriteLine($"Hello {Configuration["Profile:MachineName"]}");
Los valores de configuración se devuelven como cadenas, pero el enlace permite la construcción de
objetos. El enlace permite recuperar objetos POCO o incluso gráficos de objetos completos.
GetValue
En el ejemplo siguiente se muestra el método de extensión GetValue<T>:
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
Configuration = builder.Build();
Console.WriteLine($"Hello {Configuration["Profile:MachineName"]}");
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
using System;
using System.IO;
using Microsoft.Extensions.Configuration;
Console.WriteLine($"Height {appConfig.Window.Height}");
Console.WriteLine();
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
En ASP.NET Core 1.1 y versiones posteriores se puede usar Get<T> , que funciona con secciones
completas. Get<T> puede ser más conveniente que usar Bind . En el código siguiente se muestra cómo
usar Get<T> con el ejemplo anterior:
[Fact]
public void CanBindObjectTree()
{
var dict = new Dictionary<string, string>
{
{"App:Profile:Machine", "Rick"},
{"App:Connection:Value", "connectionstring"},
{"App:Window:Height", "11"},
{"App:Window:Width", "11"}
};
var builder = new ConfigurationBuilder();
builder.AddInMemoryCollection(dict);
var config = builder.Build();
Assert.Equal("Rick", settings.Profile.Machine);
Assert.Equal(11, settings.Window.Height);
Assert.Equal(11, settings.Window.Width);
Assert.Equal("connectionstring", settings.Connection.Value);
}
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
namespace CustomConfigurationProvider
{
public class EFConfigSource : IConfigurationSource
{
private readonly Action<DbContextOptionsBuilder> _optionsAction;
namespace CustomConfigurationProvider
{
public class EFConfigProvider : ConfigurationProvider
{
public EFConfigProvider(Action<DbContextOptionsBuilder> optionsAction)
{
OptionsAction = optionsAction;
}
namespace CustomConfigurationProvider
{
public static class EntityFrameworkExtensions
{
public static IConfigurationBuilder AddEntityFrameworkConfig(
this IConfigurationBuilder builder, Action<DbContextOptionsBuilder> setup)
{
return builder.Add(new EFConfigSource(setup));
}
}
}
using System;
using System.IO;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using CustomConfigurationProvider;
Console.WriteLine("key1={0}", config["key1"]);
Console.WriteLine("key2={0}", config["key2"]);
Console.WriteLine("key3={0}", config["key3"]);
Console.WriteLine();
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
Tenga en cuenta que el ejemplo agrega el EFConfigProvider personalizado después del proveedor de
JSON, por lo que cualquier configuración de la base de datos invalidará la configuración del archivo
appsettings.json.
Con el siguiente archivo appsettings.json:
{
"ConnectionStrings": {
"DefaultConnection": "Server=
(localdb)\\mssqllocaldb;Database=CustomConfigurationProvider;Trusted_Connection=True;MultipleActive
ResultSets=true"
},
"key1": "value_from_json_1",
"key2": "value_from_json_2",
"key3": "value_from_json_3"
}
key1=value_from_ef_1
key2=value_from_ef_2
key3=value_from_json_3
builder.AddInMemoryCollection(dict)
.AddCommandLine(args);
Configuration = builder.Build();
Console.WriteLine($"MachineName: {Configuration["Profile:MachineName"]}");
Console.WriteLine($"Left: {Configuration["App:MainWindow:Left"]}");
Console.WriteLine();
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
MachineName: MairaPC
Left: 1980
Pasar pares de clave y valor de argumento en la línea de comandos cambia los valores de
Profile:MachineName y App:MainWindow:Left :
MachineName: BartPC
Left: 1979
Un solo argumento
El valor debe seguir a un signo igual ( = ). El valor puede ser NULL (por ejemplo, mykey= ).
La clave puede tener un prefijo.
†En las asignaciones de modificador que se describen a continuación debe proporcionarse una clave
con un prefijo de un solo guion ( - ).
Comando de ejemplo:
Nota: Si -key2 no está presente en las asignaciones de modificador que se proporcionan al proveedor
de configuración, se produce una excepción FormatException .
Secuencia de dos argumentos
El valor no puede ser NULL y debe seguir a la clave separado por un espacio.
La clave debe tener un prefijo.
†En las asignaciones de modificador que se describen a continuación debe proporcionarse una clave
con un prefijo de un solo guion ( - ).
Comando de ejemplo:
Nota: Si -key1 no está presente en las asignaciones de modificador que se proporcionan al proveedor
de configuración, se produce una excepción FormatException .
Claves duplicadas
Si se proporcionan claves duplicadas, se usa el último par de clave y valor.
Asignaciones de modificador
Si realiza la configuración de compilación manualmente con ConfigurationBuilder , se puede agregar
un diccionario de asignaciones de modificador al método AddCommandLine . Las asignaciones de
modificador admiten la lógica de sustitución de nombres de clave.
Cuando se usa el diccionario de asignaciones de modificador, se comprueba en el diccionario si una
clave coincide con la clave proporcionada por un argumento de línea de comandos. Si la clave de la
línea de comandos se encuentra en el diccionario, se devuelve el valor del diccionario (el reemplazo de
la clave) para establecer la configuración. Se requiere una asignación de conmutador para cualquier
clave de línea de comandos precedida por un solo guion ( - ).
Reglas de clave del diccionario de asignaciones de modificador:
Los modificadores deben empezar por un guion ( - ) o guion doble ( -- ).
El diccionario de asignaciones de modificador no debe contener claves duplicadas.
En el ejemplo siguiente, el método GetSwitchMappings permite que los argumentos de línea de
comandos usen un prefijo de clave de un solo guión ( - ) y evita prefijos de subclave iniciales.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Configuration;
builder.AddInMemoryCollection(dict)
.AddCommandLine(args, GetSwitchMappings(dict));
Configuration = builder.Build();
Console.WriteLine($"MachineName: {Configuration["Profile:MachineName"]}");
Console.WriteLine($"Left: {Configuration["App:MainWindow:Left"]}");
Console.WriteLine();
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
dotnet run
MachineName: RickPC
Left: 1980
Después de crear el diccionario de asignaciones de modificador, contiene los datos que se muestran en
la tabla siguiente:
KEY VALOR
-MachineName Profile:MachineName
-Left App:MainWindow:Left
Se intercambian las claves de la línea de comandos. En la ventana de consola se muestran los valores
de configuración de Profile:MachineName y App:MainWindow:Left :
MachineName: ChadPC
Left: 1988
Archivo web.config
Un archivo web.config es necesario cuando la aplicación se hospeda en IIS o IIS Express. La
configuración de web.config habilita el módulo ASP.NET Core para que inicie la aplicación y configure
otros módulos y valores de configuración de IIS. Si el archivo web.config no está presente y el archivo
de proyecto incluye <Project Sdk="Microsoft.NET.Sdk.Web"> , al publicar el proyecto se crea un archivo
web.config en la salida publicada (la carpeta de publicación). Para más información, vea Host ASP.NET
Core on Windows with IIS (Hospedar ASP.NET Core en Windows con IIS ).
@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration
<!DOCTYPE html>
<html lang="en">
<head>
<title>Index Page</title>
</head>
<body>
<h1>Access configuration in a Razor Pages page</h1>
<p>Configuration["key"]: @Configuration["key"]</p>
</body>
</html>
@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration
<!DOCTYPE html>
<html lang="en">
<head>
<title>Index View</title>
</head>
<body>
<h1>Access configuration in an MVC view</h1>
<p>Configuration["key"]: @Configuration["key"]</p>
</body>
</html>
Notas adicionales
La inserción de dependencias (DI) no se establece hasta que se invoca ConfigureServices .
El sistema de configuración no es compatible con DI.
IConfiguration tiene dos especializaciones:
IConfigurationRoot Se usa para el nodo raíz. Puede desencadenar una recarga.
IConfigurationSection Representa una sección de valores de configuración. Los métodos
GetSection y GetChildren devuelven un elemento IConfigurationSection .
Use IConfigurationRoot al recargar la configuración o para acceder a todos los proveedores.
Ninguna de estas situaciones son comunes.
Recursos adicionales
Opciones
Uso de varios entornos
Almacenamiento seguro de secretos de aplicación en el desarrollo
Hospedaje en ASP.NET Core
Inserción de dependencias
Proveedor de configuración de Azure Key Vault
Patrón de opciones en ASP.NET Core
25/05/2018 • 17 minutes to read • Edit Online
El siguiente modelo de página usa la inserción de dependencias de constructor con IOptions<TOptions> para
acceder a la configuración (Pages/Index.cshtml.cs):
{
"option1": "value1_from_json",
"option2": -1,
"subsection": {
"suboption1": "subvalue1_from_json",
"suboption2": 200
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
Cuando se ejecuta la aplicación, el método OnGet del modelo de página devuelve una cadena que muestra los
valores de la clase de opción:
Index.cshtml.cs:
public IndexModel(
IOptions<MyOptions> optionsAccessor,
IOptions<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig,
IOptions<MySubOptions> subOptionsAccessor,
IOptionsSnapshot<MyOptions> snapshotOptionsAccessor,
IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
_options = optionsAccessor.Value;
_optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.Value;
_subOptions = subOptionsAccessor.Value;
_snapshotOptions = snapshotOptionsAccessor.Value;
_named_options_1 = namedOptionsAccessor.Get("named_options_1");
_named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
Puede agregar varios proveedores de configuración. Los proveedores de configuración están disponibles en
paquetes de NuGet. Se aplican en el orden en que están registrados.
Cada llamada a Configure<TOptions> agrega un servicio IConfigureOptions<TOptions> al contenedor de
servicios. En el ejemplo anterior, los valores de Option1 y Option2 se especifican en appSettings.json, pero los
valores de Option1 y Option2 se reemplazan por el delegado configurado.
Cuando se habilita más de un servicio de configuración, la última fuente de configuración especificada gana y
establece el valor de configuración. Cuando se ejecuta la aplicación, el método OnGet del modelo de página
devuelve una cadena que muestra los valores de la clase de opción:
Configuración de subopciones
La configuración de subopciones se muestra en el ejemplo #3 en la aplicación de ejemplo.
Las aplicaciones deben crear clases de opciones que pertenezcan a grupos específicos de características
(clases) en la aplicación. Los elementos de la aplicación que requieran valores de configuración deben acceder
solamente a los valores de configuración que usen.
Al enlazar opciones para la configuración, cada propiedad en el tipo de opciones se enlaza a una clave de
configuración del formulario property[:sub-property:] . Por ejemplo, la propiedad MyOptions.Option1 se
enlaza a la clave Option1 , que se lee desde la propiedad option1 en appSettings.json.
En el código siguiente, se agrega un tercer servicio IConfigureOptions<TOptions> al contenedor de servicios.
Enlaza MySubOptions a la sección subsection del archivo appsettings.json:
{
"option1": "value1_from_json",
"option2": -1,
"subsection": {
"suboption1": "subvalue1_from_json",
"suboption2": 200
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
La clase MySubOptions define propiedades, SubOption1 y SubOption2 , para mantener los valores de
subopciones (Models/MySubOptions.cs):
public class MySubOptions
{
public MySubOptions()
{
// Set default values.
SubOption1 = "value1_from_ctor";
SubOption2 = 5;
}
El método OnGet del modelo de página devuelve una cadena con los valores de subopciones
(Pages/Index.cshtml.cs):
public IndexModel(
IOptions<MyOptions> optionsAccessor,
IOptions<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig,
IOptions<MySubOptions> subOptionsAccessor,
IOptionsSnapshot<MyOptions> snapshotOptionsAccessor,
IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
_options = optionsAccessor.Value;
_optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.Value;
_subOptions = subOptionsAccessor.Value;
_snapshotOptions = snapshotOptionsAccessor.Value;
_named_options_1 = namedOptionsAccessor.Get("named_options_1");
_named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
Cuando se ejecuta la aplicación, el método OnGet devuelve una cadena que muestra los valores de clase de
subopciones:
@page
@model IndexModel
@using Microsoft.Extensions.Options
@using UsingOptionsSample.Models
@inject IOptions<MyOptions> OptionsAccessor
@{
ViewData["Title"] = "Using Options Sample";
}
<h1>@ViewData["Title"]</h1>
public IndexModel(
IOptions<MyOptions> optionsAccessor,
IOptions<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig,
IOptions<MySubOptions> subOptionsAccessor,
IOptionsSnapshot<MyOptions> snapshotOptionsAccessor,
IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
_options = optionsAccessor.Value;
_optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.Value;
_subOptions = subOptionsAccessor.Value;
_snapshotOptions = snapshotOptionsAccessor.Value;
_named_options_1 = namedOptionsAccessor.Get("named_options_1");
_named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
En la siguiente imagen se muestran los valores option1 y option2 iniciales cargados desde el archivo
appSettings.json:
Cambie los valores del archivo appSettings.json a value1_from_json UPDATED y 200 . Guarde el archivo
appSettings.json. Actualice el explorador para ver qué valores de opciones se han actualizado:
public IndexModel(
IOptions<MyOptions> optionsAccessor,
IOptions<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig,
IOptions<MySubOptions> subOptionsAccessor,
IOptionsSnapshot<MyOptions> snapshotOptionsAccessor,
IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
_options = optionsAccessor.Value;
_optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.Value;
_subOptions = subOptionsAccessor.Value;
_snapshotOptions = snapshotOptionsAccessor.Value;
_named_options_1 = namedOptionsAccessor.Get("named_options_1");
_named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
services.ConfigureAll<MyOptions>(myOptions =>
{
myOptions.Option1 = "ConfigureAll replacement value";
});
NOTE
En ASP.NET 2.0 Core y versiones posteriores, todas las opciones son instancias con nombre. Las instancias de
IConfigureOption existentes se usan para seleccionar como destino la instancia de Options.DefaultName , que es
string.Empty . IConfigureNamedOptions también implementa IConfigureOptions . La implementación
predeterminada de IOptionsFactory<TOptions> (origen de referencia tiene lógica para usar cada una de forma
adecuada. La opción con nombre null se usa para seleccionar como destino todas las instancias con nombre, en lugar
de una instancia con nombre determinada (ConfigureAll y PostConfigureAll usan esta convención).
IPostConfigureOptions
Requiere ASP.NET Core 2.0 o versiones posteriores.
Establezca la postconfiguración con IPostConfigureOptions<TOptions>. La postconfiguración se ejecuta
después de que tenga lugar toda la configuración de IConfigureOptions<TOptions>:
services.PostConfigure<MyOptions>(myOptions =>
{
myOptions.Option1 = "post_configured_option1_value";
});
Use PostConfigureAll<TOptions> para postconfigurar todas las instancias de configuración con nombre:
Vea también
Configuración
Mejorar una aplicación desde un ensamblado
externo en ASP.NET Core con IHostingStartup
04/06/2018 • 10 minutes to read • Edit Online
Implementar IHostingStartup
Crear el ensamblado
Una mejora de IHostingStartup se implementa como un ensamblado basado en una aplicación de consola sin
un punto de entrada. El ensamblado hace referencia al paquete Microsoft.AspNetCore.Hosting.Abstractions:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions"
Version="2.1.0" />
</ItemGroup>
</Project>
Un atributo HostingStartup identifica una clase como una implementación de IHostingStartup para la carga y
ejecución al compilar IWebHost. En el siguiente ejemplo, el espacio de nombres es StartupEnhancement y la
clase, StartupEnhancementHostingStartup :
[assembly: HostingStartup(typeof(StartupEnhancement.StartupEnhancementHostingStartup))]
Una clase implementa IHostingStartup . El método Configurar de la clase usa un IWebHostBuilder para agregar
mejoras a una aplicación:
namespace StartupEnhancement
{
public class StartupEnhancementHostingStartup : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
// Use the IWebHostBuilder to add app enhancements.
}
}
}
Solo se muestra parte del archivo. El nombre del ensamblado en el ejemplo es StartupEnhancement .
Actualizar el archivo de dependencias
La ubicación del tiempo de ejecución se especifica en el archivo *.deps.json. Para activar la mejora, el elemento
runtime debe especificar la ubicación del ensamblado de tiempo de ejecución de la mejora. Anteponga
lib/<TARGET_FRAMEWORK_MONIKER>/ a la ubicación de runtime :
"targets": {
".NETCoreApp,Version=v2.1": {
"StartupEnhancement/1.0.0": {
"dependencies": {
"Microsoft.AspNetCore.Hosting.Abstractions": "2.1.0"
},
"runtime": {
"lib/netcoreapp2.1/StartupEnhancement.dll": {}
}
}
}
}
En la aplicación de ejemplo, el archivo *.deps.json se modifica por medio de un script de PowerShell que se
activa automáticamente a través de un destino de compilación en el archivo de proyecto.
Activación de la mejora
Colocar el archivo de ensamblado
El archivo de ensamblado de la implementación IHostingStartup debe estar implementado en bin en la
aplicación o colocarse en el almacenamiento en tiempo de ejecución:
Para usarlo individualmente con cada usuario, coloque el ensamblado en el almacenamiento en tiempo de
ejecución del perfil del usuario en cuestión, en:
<DRIVE>\Users\<USER>\.dotnet\store\x64\<TARGET_FRAMEWORK_MONIKER>\<ENHANCEMENT_ASSEMBLY_NAME>\
<ENHANCEMENT_VERSION>\lib\<TARGET_FRAMEWORK_MONIKER>\
Para usarlo de manera global, colóquelo en el almacenamiento en tiempo de ejecución de la instalación de .NET
Core:
<DRIVE>\Program Files\dotnet\store\x64\<TARGET_FRAMEWORK_MONIKER>\<ENHANCEMENT_ASSEMBLY_NAME>\
<ENHANCEMENT_VERSION>\lib\<TARGET_FRAMEWORK_MONIKER>\
<DRIVE>\Users\<USER>\.dotnet\x64\additionalDeps\
<ENHANCEMENT_ASSEMBLY_NAME>\shared\Microsoft.NETCore.App\2.1.0\
Para usarlo de manera global, colóquelo en la carpeta additonalDeps de la instalación de .NET Core:
<DRIVE>\Program Files\dotnet\additionalDeps\<ENHANCEMENT_ASSEMBLY_NAME>\shared\Microsoft.NETCore.App\2.1.0\
La versión 2.1.0 es la versión del tiempo de ejecución compartido que la aplicación de destino usa. El tiempo
de ejecución compartido se muestra en el archivo *.runtimeconfig.json. En la aplicación de ejemplo, el tiempo de
ejecución compartido se especifica en el archivo HostingStartupSample.runtimeconfig.json.
Establecer las variables de entorno
Establezca las siguientes variables de entorno en el contexto de la aplicación que usa la mejora.
ASPNETCORE_HOSTINGSTARTUPASSEMBLIES
Solo se examinan los ensamblados de inicio de hospedaje en busca de HostingStartupAttribute . El nombre del
ensamblado de la implementación se proporciona en esta variable de entorno. En la aplicación de ejemplo, este
valor se establece en StartupDiagnostics .
Dicho valor también se puede establecer usando la configuración de host de ensamblados de inicio de
hospedaje.
DOTNET_ADDITIONAL_DEPS
Esta es la ubicación del archivo *.deps.json de la implementación.
Si el archivo está en la carpeta .dotnet del perfil de usuario para usarlo individualmente con cada usuario:
<DRIVE>\Users\<USER>\.dotnet\x64\additionalDeps\
Si el archivo está en la instalación de .NET Core para usarlo de manera global, indique la ruta de acceso
completa al archivo:
<DRIVE>\Program Files\dotnet\additionalDeps\<ENHANCEMENT_ASSEMBLY_NAME>\shared\Microsoft.NETCore.App\2.1.0\
<ENHANCEMENT_ASSEMBLY_NAME>.deps.json
%UserProfile%\.dotnet\x64\additionalDeps\StartupDiagnostics\
Para obtener ejemplos de cómo establecer variables de entorno en distintos sistemas operativos, vea Uso de
varios entornos.
Aplicación de ejemplo
La aplicación de ejemplo (cómo descargar) usa IHostingStartup para crear una herramienta de diagnóstico. Esta
herramienta agrega dos middlewares a la aplicación (mientras se inicia) que proporcionan información de
diagnóstico:
Servicios registrados
Dirección: esquema, host, ruta de acceso base, ruta de acceso, cadena de consulta
Conexión: dirección IP remota, puerto remoto, dirección IP local, puerto local, certificado de cliente
Encabezados de solicitud
Variables de entorno
Para ejecutar el ejemplo:
1. El proyecto de diagnóstico de inicio usa PowerShell para modificar el archivo StartupDiagnostics.deps.json.
PowerShell se instala de forma predeterminada en cualquier sistema operativo Windows a partir de
Windows 7 SP1 y Windows Server 2008 R2 SP1. Para obtener PowerShell en otras plataformas, vea
Instalación de Windows PowerShell.
2. Compile el proyecto de diagnóstico de inicio. Un destino de compilación en el archivo de proyecto hace lo
siguiente:
Mueve el ensamblado y los archivos de símbolos al almacenamiento en tiempo de ejecución del perfil
de usuario.
Activa el script de PowerShell para modificar el archivo StartupDiagnostics.deps.json.
Mueve el archivo StartupDiagnostics.deps.json a la carpeta additionalDeps del perfil de usuario.
3. Establezca las variables de entorno:
ASPNETCORE_HOSTINGSTARTUPASSEMBLIES : StartupDiagnostics
DOTNET_ADDITIONAL_DEPS : %UserProfile%\.dotnet\x64\additionalDeps\StartupDiagnostics\
4. Ejecute la aplicación de ejemplo.
5. Solicite al punto de conexión /services ver los servicios registrados de la aplicación. Solicite al punto de
conexión /diag ver la información de diagnóstico.
Registro en ASP.NET Core
21/06/2018 • 42 minutes to read • Edit Online
En este ejemplo se crean registros con la clase TodoController como categoría. Las categorías se explican
más adelante en este artículo.
ASP.NET Core no proporciona métodos de registrador asincrónicos porque el registro debe ser tan rápido
que el costo de usarlos no vale la pena. Si se encuentra en una situación en la que no sea así, considere la
posibilidad de cambiar el modo de registro. Si el almacén de datos es lento, escriba primero los mensajes de
registro en un almacén rápido y, después, muévalos a un almacén de baja velocidad. Por ejemplo, realice el
registro en una cola de mensajes que otro proceso lea y conserve en almacenamiento lento.
Cómo agregar proveedores
ASP.NET Core 2.x
ASP.NET Core 1.x
Un proveedor de registro toma los mensajes que se crean con un objeto ILogger y los muestra o almacena.
Por ejemplo, el proveedor de la consola muestra mensajes en la consola y el proveedor de Azure App Service
puede almacenarlos en Azure Blob Storage.
Para usar un proveedor, llame al método de extensión Add<ProviderName> del proveedor en Program.cs:
webHost.Run();
}
Puede encontrar información sobre cada proveedor de registro integrado y vínculos a proveedores de
registro de terceros más adelante en el artículo.
Los registros creados por las llamadas a ILogger que se muestran en la sección anterior comienzan por
"TodoApi.Controllers.TodoController". Los registros que comienzan por categorías de "Microsoft" son de
ASP.NET Core. El propio ASP.NET Core y el código de la aplicación usan la misma API y los mismos
proveedores de registro.
En el resto de este artículo se explican algunos detalles y opciones para el registro.
Paquetes NuGet
Las interfaces ILogger e ILoggerFactory se encuentran en Microsoft.Extensions.Logging.Abstractions, y sus
implementaciones predeterminadas en Microsoft.Extensions.Logging.
Categoría de registro
Con cada registro que cree se incluye una categoría. La categoría se especifica cuando se crea un objeto
ILogger . La categoría puede ser cualquier cadena, pero una convención consiste en usar el nombre completo
de la clase desde la que se escriben los registros. Por ejemplo: "TodoApi.Controllers.TodoController".
Puede especificar la categoría como una cadena o usar un método de extensión que derive la categoría del
tipo. Para especificar la categoría como una cadena, llame a CreateLogger en una instancia de ILoggerFactory
, como se muestra a continuación.
public class TodoController : Controller
{
private readonly ITodoRepository _todoRepository;
private readonly ILogger _logger;
En la mayoría de los casos, será más fácil usar ILogger<T> , como en el ejemplo siguiente.
Nivel de registro
Cada vez que se escribe un registro, se especifica su LogLevel. El nivel de registro indica el grado de gravedad
o importancia. Por ejemplo, es posible que escriba un registro Information cuando un método finaliza
normalmente, un registro Warning cuando un método devuelve un código de retorno 404 y un registro
Error cuando capture una excepción inesperada.
En el ejemplo de código siguiente, los nombres de los métodos (por ejemplo, LogWarning ) especifican el nivel
de registro. El primer parámetro es el Id. del evento del Registro. El segundo parámetro es una plantilla de
mensaje con marcadores de posición para los valores de argumento proporcionados por el resto de
parámetros de método. Los parámetros de método se explican detalladamente más adelante en este artículo.
Los métodos de registro que incluyen el nivel en el nombre del método son métodos de extensión para
ILogger. En segundo plano, estos métodos llaman a un método Log que toma un parámetro LogLevel .
Puede llamar directamente al método Log en lugar de a uno de estos métodos de extensión, pero la sintaxis
es relativamente complicada. Para más información, vea la interfaz ILogger y el código fuente de las
extensiones de registrador.
ASP.NET Core define los niveles de registro siguientes, que aquí se ordenan de menor a mayor gravedad.
Seguimiento = 0
Para la información que solo es útil para un desarrollador que depura un problema. Estos mensajes
pueden contener datos confidenciales de la aplicación, por lo que no deben habilitarse en un entorno
de producción. Deshabilitado de forma predeterminada. Ejemplo:
Credentials: {"User":"someuser", "Password":"P@ssword"}
Depurar = 1
Para la información que tiene utilidad a corto plazo durante el desarrollo y la depuración. Ejemplo:
Entering method Configure with flag set to true. normalmente los registros de nivel Debug no se
habilitarían en producción, a menos que se esté solucionando un problema, debido al elevado volumen
de registros.
Información = 2
Para realizar el seguimiento del flujo general de la aplicación. Estos registros suelen tener algún valor a
largo plazo. Ejemplo: Request received for path /api/todo
Advertencia = 3
Para los eventos anómalos o inesperados en el flujo de la aplicación. Estos pueden incluir errores u
otras condiciones que no hacen que la aplicación se detenga, pero que puede que sea necesario
investigar. Las excepciones controladas son un lugar común para usar el nivel de registro Warning .
Ejemplo: FileNotFoundException for file quotes.txt.
Error = 4
Para los errores y excepciones que no se pueden controlar. Estos mensajes indican un error en la
actividad u operación actual (por ejemplo, la solicitud HTTP actual), no un error de toda la aplicación.
Mensaje de registro de ejemplo: Cannot insert record due to duplicate key violation.
Crítico = 5
Para los errores que requieren atención inmediata. Ejemplos: escenarios de pérdida de datos, espacio
en disco insuficiente.
Puede usar el nivel de registro para controlar la cantidad de salida del registro que se escribe en un medio de
almacenamiento determinado o ventana de presentación. Por ejemplo, en producción es posible que quiera
que todos los registros de nivel Information e inferiores vayan a un almacén de datos de volumen y que
todos los registros de nivel Warning y superiores vayan un almacén de datos de valor. Durante el desarrollo,
es posible que normalmente envíe los registros de gravedad Warning o superior a la consola. Después, si
tiene que solucionar problemas, puede agregar el nivel Debug . En la sección Filtrado del registro de este
artículo se explica cómo controlar los niveles de registro que controla un proveedor.
La plataforma ASP.NET Core escribe registros de nivel Debug para los eventos de la plataforma. En los
ejemplos de registro anteriores de este artículo se excluyeron los registros por debajo del nivel Information ,
por lo que no se mostraron los registros de nivel Debug . Este es un ejemplo de registros de consola si ejecuta
la aplicación de ejemplo configurada para mostrar Debug y registros superiores para el proveedor de la
consola.
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET http://localhost:62555/api/todo/0
dbug: Microsoft.AspNetCore.Routing.Tree.TreeRouter[1]
Request successfully matched the route with name 'GetTodo' and template 'api/Todo/{id}'.
dbug: Microsoft.AspNetCore.Mvc.Internal.ActionSelector[2]
Action 'TodoApi.Controllers.TodoController.Update (TodoApi)' with id '089d59b6-92ec-472d-b552-
cc613dfd625d' did not match the constraint 'Microsoft.AspNetCore.Mvc.Internal.HttpMethodActionConstraint'
dbug: Microsoft.AspNetCore.Mvc.Internal.ActionSelector[2]
Action 'TodoApi.Controllers.TodoController.Delete (TodoApi)' with id 'f3476abe-4bd9-4ad3-9261-
3ead09607366' did not match the constraint 'Microsoft.AspNetCore.Mvc.Internal.HttpMethodActionConstraint'
dbug: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Executing action TodoApi.Controllers.TodoController.GetById (TodoApi)
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Executing action method TodoApi.Controllers.TodoController.GetById (TodoApi) with arguments (0) -
ModelState is Valid
info: TodoApi.Controllers.TodoController[1002]
Getting item 0
warn: TodoApi.Controllers.TodoController[4000]
GetById(0) NOT FOUND
dbug: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Executed action method TodoApi.Controllers.TodoController.GetById (TodoApi), returned result
Microsoft.AspNetCore.Mvc.NotFoundResult.
info: Microsoft.AspNetCore.Mvc.StatusCodeResult[1]
Executing HttpStatusCodeResult, setting HTTP status code 404
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Executed action TodoApi.Controllers.TodoController.GetById (TodoApi) in 0.8788ms
dbug: Microsoft.AspNetCore.Server.Kestrel[9]
Connection id "0HL6L7NEFF2QD" completed keep alive response.
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 2.7286ms 404
info: TodoApi.Controllers.TodoController[1002]
Getting item invalidid
warn: TodoApi.Controllers.TodoController[4000]
GetById(invalidid) NOT FOUND
El orden de los marcadores de posición, no sus nombres, determina qué parámetros se usan para
proporcionar sus valores. Si tiene el siguiente código:
string p1 = "parm1";
string p2 = "parm2";
_logger.LogInformation("Parameter values: {p2}, {p1}", p1, p2);
La plataforma de registro aplica este formato de mensajes para que los proveedores de registro puedan
implementar el registro semántico, también conocido como registro estructurado. Dado que los propios
argumentos se pasan al sistema de registro, no solo la plantilla de mensaje con formato, los proveedores de
registro pueden almacenar los valores de parámetros como campos además de la plantilla de mensaje. Si va a
dirigir la salida del registro a Azure Table Storage y la llamada al método de registrador tiene el aspecto
siguiente:
Cada entidad de la Tabla de Azure puede tener propiedades ID y RequestTime , lo que simplifica las consultas
en los datos de registro. Puede buscar todos los registros dentro de un intervalo RequestTime determinado
sin necesidad de analizar el tiempo de espera del mensaje de texto.
Excepciones de registro
Los métodos de registrador tienen sobrecargas que le permiten pasar una excepción, como en el ejemplo
siguiente:
Cada proveedor controla la información de la excepción de maneras diferentes. Este es un ejemplo de salida
del proveedor de depuración del código mostrado anteriormente.
webHost.Run();
}
Los datos de configuración especifican niveles de registro mínimo por proveedor y categoría, como en el
ejemplo siguiente:
{
"Logging": {
"Debug": {
"LogLevel": {
"Default": "Information"
}
},
"Console": {
"IncludeScopes": false,
"LogLevel": {
"Microsoft.AspNetCore.Mvc.Razor.Internal": "Warning",
"Microsoft.AspNetCore.Mvc.Razor.Razor": "Debug",
"Microsoft.AspNetCore.Mvc.Razor": "Error",
"Default": "Information"
}
},
"LogLevel": {
"Default": "Debug"
}
}
}
Este archivo JSON crea seis reglas de filtro, una para el proveedor de depuración, cuatro para el proveedor de
la consola y una que se aplica a todos los proveedores. Más adelante verá que solo una de estas reglas se
elige para cada proveedor cuando se crea un objeto ILogger .
Reglas de filtro en el código
Puede registrar las reglas de filtro en el código, como se muestra en el ejemplo siguiente:
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureLogging(logging =>
logging.AddFilter("System", LogLevel.Debug)
.AddFilter<DebugLoggerProvider>("Microsoft", LogLevel.Trace))
.Build();
CATEGORÍAS QUE
NÚMERO PROVEEDOR COMIENZAN POR... NIVEL DE REGISTRO MÍNIMO
Cuando se crea un objeto ILogger con el que escribir los registros, el objeto ILoggerFactory selecciona una
sola regla por proveedor para aplicar a ese registrador. Todos los mensajes escritos por ese objeto ILogger se
filtran según las reglas seleccionadas. De las reglas disponibles se selecciona la más específica posible para
cada par de categoría y proveedor.
Cuando se crea un ILogger para una categoría determinada, se usa el algoritmo siguiente para cada
proveedor:
Se seleccionan todas las reglas que coinciden con el proveedor o su alias. Si no se encuentra ninguna, se
seleccionan todas las reglas con un proveedor vacío.
Del resultado del paso anterior, se seleccionan las reglas con el prefijo de categoría coincidente más largo.
Si no se encuentra ninguna, se seleccionan todas las reglas que no especifican una categoría.
Si se seleccionan varias reglas, se toma la última.
Si no se selecciona ninguna regla, se usa MinimumLevel .
Por ejemplo, supongamos que tiene la lista de reglas anterior y crea un objeto ILogger para la categoría
"Microsoft.AspNetCore.Mvc.Razor.RazorViewEngine":
Para el proveedor de depuración, se aplican las reglas 1, 6 y 8. La regla 8 es la más específica, por lo que se
selecciona.
Para el proveedor de la consola, se aplican las reglas 3, 4, 5 y 6. La regla 3 es la más específica.
Cuando crea registros con un ILogger para la categoría
"Microsoft.AspNetCore.Mvc.Razor.RazorViewEngine", los registros de nivel Trace y superiores se dirigirán al
proveedor de depuración y los registros de nivel Debug y superiores se dirigirán al proveedor de consola.
Alias de proveedor
Puede usar el nombre de tipo para especificar un proveedor en la configuración, pero cada proveedor define
un alias más breve que es más fácil de usar. Para los proveedores integrados, use los alias siguientes:
Consola
Depuración
EventLog
AzureAppServices
TraceSource
EventSource
Nivel mínimo predeterminado
Hay una configuración de nivel mínimo que solo tiene efecto si no se aplica ninguna regla de configuración o
código para un proveedor y una categoría determinados. En el ejemplo siguiente se muestra cómo establecer
el nivel mínimo:
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureLogging(logging => logging.SetMinimumLevel(LogLevel.Warning))
.Build();
Si no establece explícitamente el nivel mínimo, el valor predeterminado es Information , lo que significa que
los registros Trace y Debug se omiten.
Funciones de filtro
Puede escribir código en una función de filtro para aplicar las reglas de filtrado. Se invoca una función de filtro
para todos los proveedores y categorías que no tienen reglas asignadas mediante configuración o código. El
código de la función tiene acceso al tipo de proveedor, categoría y nivel de registro para decidir si se debe
registrar un mensaje. Por ejemplo:
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureLogging(logBuilder =>
{
logBuilder.AddFilter((provider, category, logLevel) =>
{
if (provider == "Microsoft.Extensions.Logging.Console.ConsoleLoggerProvider" &&
category == "TodoApi.Controllers.TodoController")
{
return false;
}
return true;
});
})
.Build();
Ámbitos de registro
Puede agrupar un conjunto de operaciones lógicas dentro de un ámbito para adjuntar los mismos datos para
cada registro que se crea como parte de ese conjunto. Por ejemplo, es posible que quiera que todos los
registros creados como parte del procesamiento de una transacción incluyan el identificador de la transacción.
Un ámbito es un tipo IDisposable devuelto por el método ILogger.BeginScope<TState> que se conserva
hasta que se elimina. Para usar un ámbito, las llamadas de registrador se encapsulan en un bloque using ,
como se muestra aquí:
NOTE
Es necesario configurar la opción del registrador de consola IncludeScopes para habilitar el registro basado en el
ámbito. La configuración de IncludeScopes con archivos de configuración appsettings estará disponible con la
versión de ASP.NET Core 2.1.
info: TodoApi.Controllers.TodoController[1002]
=> RequestId:0HKV9C49II9CK RequestPath:/api/todo/0 => TodoApi.Controllers.TodoController.GetById
(TodoApi) => Message attached to logs created in the using block
Getting item 0
warn: TodoApi.Controllers.TodoController[4000]
=> RequestId:0HKV9C49II9CK RequestPath:/api/todo/0 => TodoApi.Controllers.TodoController.GetById
(TodoApi) => Message attached to logs created in the using block
GetById(0) NOT FOUND
Proveedores de registro integrados
ASP.NET Core incluye los proveedores siguientes:
Consola
Depurar
EventSource
EventLog
TraceSource
Azure App Service
Proveedor de la consola
El paquete de proveedor Microsoft.Extensions.Logging.Console envía la salida del registro a la consola.
ASP.NET Core 2.x
ASP.NET Core 1.x
logging.AddConsole()
Proveedor de depuración
El paquete de proveedor Microsoft.Extensions.Logging.Debug escribe la salida del registro mediante la clase
System.Diagnostics.Debug (llamadas a métodos Debug.WriteLine ).
En Linux, este proveedor escribe registros en /var/log/message.
ASP.NET Core 2.x
ASP.NET Core 1.x
logging.AddDebug()
Proveedor EventSource
Para las aplicaciones que tienen como destino ASP.NET Core 1.1.0 o superior, el paquete de proveedor
Microsoft.Extensions.Logging.EventSource puede implementar el seguimiento de eventos. En Windows, usa
ETW. Es un proveedor multiplataforma, pero todavía no hay herramientas de recopilación y visualización de
eventos para Linux o macOS.
ASP.NET Core 2.x
ASP.NET Core 1.x
logging.AddEventSourceLogger()
Una buena manera de recopilar y ver los registros es usar la utilidad PerfView. Hay otras herramientas para
ver los registros ETW, pero PerfView proporciona la mejor experiencia para trabajar con los eventos ETW
emitidos por ASP.NET.
Para configurar PerfView para la recopilación de eventos registrados por este proveedor, agregue la cadena
*Microsoft-Extensions-Logging a la lista Proveedores adicionales. ( No olvide el asterisco al principio de la
cadena).
Proveedor EventLog de Windows
El paquete de proveedor Microsoft.Extensions.Logging.EventLog envía la salida del registro al Registro de
eventos de Windows.
ASP.NET Core 2.x
ASP.NET Core 1.x
logging.AddEventLog()
Proveedor TraceSource
El paquete de proveedor Microsoft.Extensions.Logging.TraceSource usa las bibliotecas y proveedores de
System.Diagnostics.TraceSource.
ASP.NET Core 2.x
ASP.NET Core 1.x
logging.AddTraceSource(sourceSwitchName);
logging.AddAzureWebAppDiagnostics();
Cuando se realiza una implementación en una aplicación de App Service, la aplicación respeta la
configuración de la sección Registros de diagnóstico situada en la página App Service de Azure Portal.
Cuando se actualiza esta configuración, los cambios se aplican inmediatamente sin necesidad de reiniciar ni
de volver a implementar la aplicación.
La ubicación predeterminada de los archivos de registro es la carpeta D:\home\LogFiles\Application y el
nombre de archivo predeterminado es diagnostics-aaaammdd.txt. El límite de tamaño de archivo
predeterminado es 10 MB, y el número máximo predeterminado de archivos que se conservan es 2. El
nombre de blob predeterminado es {nombre-de-la -aplicación}{marca de tiempo }/aaaa/mm/dd/hh/{guid }-
applicationLog.txt. Para más información sobre el comportamiento predeterminado, vea
AzureAppServicesDiagnosticsSettings.
El proveedor solo funciona cuando el proyecto se ejecuta en el entorno de Azure. No tiene ningún efecto
cuando el proyecto se ejecuta de manera local (no escribe en los archivos locales ni en el almacenamiento de
desarrollo local de blobs).
Navegue hasta la página Secuencias de registro para ver los mensajes de la aplicación. Se registran por la
aplicación a través de la interfaz ILogger .
Recursos adicionales
Registro de alto rendimiento con LoggerMessage
Registro de alto rendimiento con LoggerMessage en
ASP.NET Core
14/05/2018 • 13 minutes to read • Edit Online
LoggerMessage proporciona las siguientes ventajas de rendimiento frente a los métodos de extensión del
registrador:
Los métodos de extensión del registrador requieren la conversión boxing de tipos de valor, como int , en
object . El patrón LoggerMessage impide la conversión boxing mediante métodos de extensión y campos
Action estáticos con parámetros fuertemente tipados.
Los métodos de extensión del registrador deben analizar la plantilla de mensaje (cadena de formato con
nombre) cada vez que se escribe un mensaje de registro. LoggerMessage solo necesita analizar una vez una
plantilla cuando se define el mensaje.
Vea o descargue el código de ejemplo (cómo descargarlo)
La aplicación de ejemplo muestra las características de LoggerMessage con un sistema de seguimiento de citas
básico. La aplicación agrega y elimina citas mediante una base de datos en memoria. A medida que se producen
estas operaciones, se generan mensajes de registro mediante el patrón LoggerMessage .
LoggerMessage.Define
Define(LogLevel, EventId, String) crea un delegado Action para registrar un mensaje. Las sobrecargas Define
permiten pasar hasta seis parámetros de tipo a una cadena de formato con nombre (plantilla).
La cadena proporcionada al método Define es una plantilla y no una cadena interpolada. Los marcadores de
posición se rellenan en el orden en que se especifican los tipos. Los nombres de los marcadores de posición en la
plantilla deben ser descriptivos y coherentes entre las plantillas. Sirven como nombres de propiedad en los datos
estructurados del registro. Se recomienda el uso de la grafía Pascal para los nombres de los marcadores de
posición. Por ejemplo: {Count} , {FirstName} .
Cada mensaje de registro es un delegado Action que se mantiene en un campo estático creado por
LoggerMessage.Define . Por ejemplo, la aplicación de ejemplo crea un campo que describe un mensaje de registro
para una solicitud GET para la página de índice (Internal/LoggerExtensions.cs):
_indexPageRequested = LoggerMessage.Define(
LogLevel.Information,
new EventId(1, nameof(IndexPageRequested)),
"GET request for Index page");
Los almacenes de registro estructurado pueden usar el nombre de evento cuando se suministra con el
identificador de evento para enriquecer el registro. Por ejemplo, Serilog usa el nombre de evento.
El delegado Action se invoca mediante un método de extensión fuertemente tipado. El método
IndexPageRequested registra un mensaje para una solicitud GET de página de índice en la aplicación de ejemplo:
info: LoggerMessageSample.Pages.IndexModel[1]
=> RequestId:0HL90M6E7PHK4:00000001 RequestPath:/ => /Index
GET request for Index page
Para pasar parámetros a un mensaje de registro, defina hasta seis tipos al crear el campo estático. La aplicación de
ejemplo registra una cadena cuando se agrega una cita. Para ello, define un tipo string para el campo Action :
La plantilla de mensaje de registro del delegado recibe sus valores de marcador de posición a partir de los tipos
proporcionados. La aplicación de ejemplo define un delegado para agregar una cita cuando el parámetro de la cita
es string :
_quoteAdded = LoggerMessage.Define<string>(
LogLevel.Information,
new EventId(2, nameof(QuoteAdded)),
"Quote added (Quote = '{Quote}')");
El método de extensión estático para agregar una cita, QuoteAdded , recibe el valor de argumento de la cita y lo pasa
al delegado Action :
public static void QuoteAdded(this ILogger logger, string quote)
{
_quoteAdded(logger, quote, null);
}
En el modelo de página para la página de índice (Pages/Index.cshtml.cs), se llama a QuoteAdded para registrar el
mensaje:
_logger.QuoteAdded(Quote.Text);
return RedirectToPage();
}
info: LoggerMessageSample.Pages.IndexModel[2]
=> RequestId:0HL90M6E7PHK5:0000000A RequestPath:/ => /Index
Quote added (Quote = 'You can avoid reality, but you cannot avoid the consequences of avoiding reality.
- Ayn Rand')
La aplicación de ejemplo implementa un patrón try – catch para la eliminación de la cita. Se registra un mensaje
informativo si se realiza correctamente una operación de eliminación. Se registra un mensaje de error para una
operación de eliminación si se produce una excepción. El mensaje de registro de la operación de eliminación con
error incluye el seguimiento de la pila de excepciones (Internal/LoggerExtensions.cs):
_quoteDeleteFailed = LoggerMessage.Define<int>(
LogLevel.Error,
new EventId(5, nameof(QuoteDeleteFailed)),
"Quote delete failed (Id = {Id})");
public static void QuoteDeleted(this ILogger logger, string quote, int id)
{
_quoteDeleted(logger, quote, id, null);
}
public static void QuoteDeleteFailed(this ILogger logger, int id, Exception ex)
{
_quoteDeleteFailed(logger, id, ex);
}
En el modelo de página para la página de índice, una operación correcta de eliminación de cita llama al método
QuoteDeleted en el registrador. Cuando no se encuentra una cita para su eliminación, se produce una excepción
ArgumentNullException . La excepción se captura mediante la instrucción try – catch y se registra mediante una
llamada al método QuoteDeleteFailed en el registrador en el bloque catch (Pages/Index.cshtml.cs):
_logger.QuoteDeleted(quote.Text, id);
}
catch (ArgumentNullException ex)
{
_logger.QuoteDeleteFailed(id, ex);
}
return RedirectToPage();
}
info: LoggerMessageSample.Pages.IndexModel[4]
=> RequestId:0HL90M6E7PHK5:00000016 RequestPath:/ => /Index
Quote deleted (Quote = 'You can avoid reality, but you cannot avoid the consequences of avoiding
reality. - Ayn Rand' Id = 1)
Cuando se produzca un error en la eliminación de una cita, inspeccione la salida de la consola de la aplicación.
Tenga en cuenta que la excepción se incluye en el mensaje del registro:
fail: LoggerMessageSample.Pages.IndexModel[5]
=> RequestId:0HL90M6E7PHK5:00000010 RequestPath:/ => /Index
Quote delete failed (Id = 999)
System.ArgumentNullException: Value cannot be null.
Parameter name: entity
at Microsoft.EntityFrameworkCore.Utilities.Check.NotNull[T](T value, String parameterName)
at Microsoft.EntityFrameworkCore.DbContext.Remove[TEntity](TEntity entity)
at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.Remove(TEntity entity)
at LoggerMessageSample.Pages.IndexModel.<OnPostDeleteQuoteAsync>d__14.MoveNext() in
<PATH>\sample\Pages\Index.cshtml.cs:line 87
LoggerMessage.DefineScope
DefineScope(String) crea un delegado Func para definir un ámbito de registro. Las sobrecargas DefineScope
permiten pasar hasta tres parámetros de tipo a una cadena de formato con nombre (plantilla).
Al igual que sucede con el método Define , la cadena proporcionada al método DefineScope es una plantilla y no
una cadena interpolada. Los marcadores de posición se rellenan en el orden en que se especifican los tipos. Los
nombres de los marcadores de posición en la plantilla deben ser descriptivos y coherentes entre las plantillas.
Sirven como nombres de propiedad en los datos estructurados del registro. Se recomienda el uso de la grafía
Pascal para los nombres de los marcadores de posición. Por ejemplo: {Count} , {FirstName} .
Defina un ámbito de registro para aplicarlo a una serie de mensajes de registro mediante el método
DefineScope(String).
La aplicación de ejemplo tiene un botón Borrar todo para eliminar todas las citas de la base de datos. Para
eliminar las citas, se van quitando de una en una. Cada vez que se elimina una cita, se llama al método
QuoteDeleted en el registrador. Se agrega un ámbito de registro a estos mensajes de registro.
Es necesario establecer IncludeScopes en las aplicaciones de ASP.NET Core 2.0 para habilitar los ámbitos de
registro. Está previsto incluir en la versión ASP.NET Core 2.1 la opción de establecer IncludeScopes a través de
archivos de configuración appsettings.
La aplicación de ejemplo borra los demás proveedores y agrega filtros para reducir la salida del registro. Así
resulta más fácil ver los mensajes de registro del ejemplo que muestran las características de LoggerMessage .
Para crear un ámbito de registro, agregue un campo para que contenga un delegado Func para el ámbito. La
aplicación de ejemplo crea un campo denominado _allQuotesDeletedScope (Internal/LoggerExtensions.cs):
Use DefineScope para crear el delegado. Pueden especificarse hasta tres tipos para usarlos como argumentos de
plantilla cuando se invoca el delegado. La aplicación de ejemplo usa una plantilla de mensaje que incluye el
número de citas eliminadas (un tipo int ):
Proporcione un método de extensión estático para el mensaje de registro. Incluya todos los parámetros de tipo
para propiedades con nombre que aparezcan en la plantilla de mensaje. La aplicación de ejemplo toma un valor de
número count de citas que se van a eliminar y devuelve _allQuotesDeletedScope :
using (_logger.AllQuotesDeletedScope(quoteCount))
{
foreach (Quote quote in _db.Quotes)
{
_db.Quotes.Remove(quote);
_logger.QuoteDeleted(quote.Text, quote.Id);
}
await _db.SaveChangesAsync();
}
return RedirectToPage();
}
info: LoggerMessageSample.Pages.IndexModel[4]
=> RequestId:0HL90M6E7PHK5:0000002E RequestPath:/ => /Index => All quotes deleted (Count = 3)
Quote deleted (Quote = 'Quote 1' Id = 2)
info: LoggerMessageSample.Pages.IndexModel[4]
=> RequestId:0HL90M6E7PHK5:0000002E RequestPath:/ => /Index => All quotes deleted (Count = 3)
Quote deleted (Quote = 'Quote 2' Id = 3)
info: LoggerMessageSample.Pages.IndexModel[4]
=> RequestId:0HL90M6E7PHK5:0000002E RequestPath:/ => /Index => All quotes deleted (Count = 3)
Quote deleted (Quote = 'Quote 3' Id = 4)
Vea también
Registro
Controlar errores en ASP.NET Core
25/05/2018 • 10 minutes to read • Edit Online
Coloque UseDeveloperExceptionPage antes del software intermedio en el que quiera capturar excepciones, como
app.UseMvc .
WARNING
Habilite la página de excepciones para el desarrollador solo cuando la aplicación se ejecute en el entorno de
desarrollo. No le interesa compartir públicamente información detallada sobre las excepciones cuando la aplicación se
ejecute en producción. Más información sobre la configuración de entornos.
Para ver la página de excepciones para el desarrollador, ejecute la aplicación de ejemplo con el entorno
establecido en Development y agregue ?throw=true a la URL base de la aplicación. La página incluye varias
pestañas con información sobre la excepción y la solicitud. La primera pestaña incluye un seguimiento de la pila.
En la pestaña siguiente se muestran los parámetros de cadena de consulta, si los hay.
Esta solicitud no tenía cookies, pero en caso de que las tuviera, aparecerían en la pestaña Cookies. Puede ver los
encabezados que se han pasado en la última pestaña.
Configuración de una página personalizada de control de excepciones
Se recomienda configurar una página de controlador de excepciones para usarla cuando la aplicación no se
ejecute en el entorno Development .
En una aplicación MVC, no decore explícitamente el método de acción del controlador de errores con atributos
de método HTTP, como HttpGet . El uso de verbos explícitos podría impedir que algunas solicitudes llegasen al
método.
[Route("/Error")]
public IActionResult Index()
{
// Handle error here
}
Configuración de páginas de códigos de estado
Una aplicación no proporciona de forma predeterminada una página de códigos de estado enriquecida para
códigos de estado HTTP como 404 No encontrado. Para proporcionarlas, configure el middleware de páginas
de código de estado agregando una línea al método Startup.Configure :
app.UseStatusCodePages();
El middleware de páginas de código de estado agrega de forma predeterminada controladores simples de solo
texto para códigos de estado comunes, como 404:
El middleware admite diversos métodos de extensión. Uno de ellos es una expresión lambda:
app.UseStatusCodePagesWithRedirects("/error/{0}");
El método de nueva ejecución devuelve el código de estado original al cliente, pero también ejecuta el
controlador para la dirección URL de redireccionamiento:
app.UseStatusCodePagesWithReExecute("/error/{0}");
Las páginas de códigos de estado se pueden deshabilitar en solicitudes específicas en un método de controlador
de páginas de Razor o en un controlador MVC. Para deshabilitar páginas de códigos de estado, intente
recuperar IStatusCodePagesFeature de la colección HttpContext.Features de la solicitud y deshabilite la
característica si está disponible:
var statusCodePagesFeature = HttpContext.Features.Get<IStatusCodePagesFeature>();
if (statusCodePagesFeature != null)
{
statusCodePagesFeature.Enabled = false;
}
Para recorrer en iteración el contenido del directorio u obtener información de un archivo específico, puede
proporcionar una subruta de acceso.
Para solicitar un proveedor de un controlador, especifíquelo en el constructor del controlador y asígnelo a un
campo local. Utilice la instancia local de los métodos de acción:
public class HomeController : Controller
{
private readonly IFileProvider _fileProvider;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
namespace FileProviderSample
{
public class Startup
{
private IHostingEnvironment _hostingEnvironment;
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
_hostingEnvironment = env;
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
<h2>Folder Contents</h2>
<ul>
@foreach (IFileInfo item in Model)
{
if (item.IsDirectory)
{
<li><strong>@item.Name</strong></li>
}
else
{
<li>@item.Name - @item.Length bytes</li>
}
}
</ul>
Resultado:
EmbeddedFileProvider
EmbeddedFileProvider se utiliza para tener acceso a archivos insertados en ensamblados. En .NET Core, se insertan
archivos en un ensamblado con el elemento <EmbeddedResource> en el archivo .csproj:
<ItemGroup>
<EmbeddedResource Include="Resource.txt;**\*.js"
Exclude="bin\**;obj\**;**\*.xproj;packages\**;@(EmbeddedResource)" />
<Content Update="wwwroot\**\*;Views\**\*;Areas\**\Views;appsettings.json;web.config">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
Puede usar patrones de comodines para especificar archivos que se insertarán en el ensamblado. Estos patrones
se pueden usar para que coincidan con uno o más archivos.
NOTE
Es improbable que alguna vez quiera insertar cada archivo .js del proyecto en su ensamblado; el ejemplo anterior se incluye
solo con fines de demostración.
El fragmento de código anterior muestra cómo crear EmbeddedFileProvider con acceso al ensamblado que se está
ejecutando actualmente.
La actualización de la aplicación de ejemplo para usar EmbeddedFileProvider genera la siguiente salida:
NOTE
Los recursos insertados no exponen directorios. En su lugar, la ruta de acceso al recurso (a través de su espacio de nombres)
se inserta en su nombre de archivo con separadores . .
TIP
El constructor EmbeddedFileProvider acepta un parámetro baseNamespace opcional. Si se especifica este parámetro, las
llamadas a GetDirectoryContents tendrán como ámbito esos recursos en el espacio de nombres proporcionado.
CompositeFileProvider
CompositeFileProvider combina instancias de IFileProvider , y expone una única interfaz para trabajar con
archivos de varios proveedores. Al crear CompositeFileProvider , se pasan una o varias instancias de
IFileProvider a su constructor:
Actualizar la aplicación de ejemplo para que use un proveedor CompositeFileProvider que incluya los proveedores
físicos y los insertados que se configuraron anteriormente, produce la siguiente salida:
Observación de cambios
El método IFileProvider Watch proporciona una manera de ver uno o más archivos o directorios para detectar
cambios. Este método acepta una cadena de ruta de acceso, que puede usar patrones de comodines para
especificar varios archivos y devuelve un token IChangeToken . Este token expone una propiedad HasChanged que
se puede inspeccionar, así como un método RegisterChangeCallback al que se llama cuando se detectan cambios
en la cadena de ruta de acceso especificada. Tenga en cuenta que cada token de cambio solo llama a su devolución
de llamada asociada en respuesta a un único cambio. Para habilitar la supervisión constante, puede usar
TaskCompletionSource tal y como se muestra a continuación, o volver a crear instancias de IChangeToken en
respuesta a los cambios.
En el ejemplo de este artículo, se configura una aplicación de consola para mostrar un mensaje cada vez que se
modifica un archivo de texto:
while (true)
{
MainAsync().GetAwaiter().GetResult();
}
}
token.RegisterChangeCallback(state =>
((TaskCompletionSource<object>)state).TrySetResult(null), tcs);
await tcs.Task.ConfigureAwait(false);
Console.WriteLine("quotes.txt changed");
}
NOTE
Algunos sistemas de archivos, como contenedores de Docker y recursos compartidos de red, no pueden enviar notificaciones
de cambio de forma confiable. Establezca la variable de entorno DOTNET_USE_POLLINGFILEWATCHER en 1 o true para
sondear el sistema de archivos en busca de cambios cada 4 segundos.
Patrones de comodines
Las rutas de acceso del sistema de archivos utilizan patrones de caracteres comodín denominados patrones de
comodines. Estos sencillos modelos se pueden usar para especificar grupos de archivos. Los dos caracteres
comodín son * y ** .
*
Coincide con cualquier elemento del nivel de carpeta actual, con cualquier nombre de archivo o con cualquier
extensión de archivo. Las coincidencias finalizan con los caracteres / y . en la ruta de acceso de archivo.
**
Coincide con cualquier elemento de varios niveles de directorios. Puede usarse para coincidir de forma recursiva
con muchos archivos dentro de una jerarquía de directorios.
Ejemplos de patrones de uso de comodines
directory/file.txt
Coincide con todos los archivos que tengan la extensión .txt en un directorio específico.
directory/*/bower.json
Coincide con todos los archivos bower.json que estén en directorios exactamente un nivel por debajo del
directorio directory .
directory/**/*.txt
Coincide con todos los archivos cuya extensión sea .txt y se encuentren en cualquier lugar del directorio
directory .
Las aplicaciones .NET configuran e inician un host. El host es responsable de la administración del inicio y la
duración de la aplicación. Hay dos API host disponibles para su uso:
Host web: indicado para el hospedaje de aplicaciones web.
Host genérico (ASP.NET Core 2.1 y versiones posteriores): indicado para el hospedaje de aplicaciones que
no sean web, por ejemplo, las que ejecutan tareas en segundo plano. En una versión posterior, el host
genérico será adecuado para hospedar aplicaciones de cualquier tipo, incluidas las web. Llegado el
momento, el host genérico reemplazará el host web.
Para hospedar aplicaciones web ASP.NET Core, los desarrolladores deben usar el host web basado en
WebHostBuilder. Para hospedar aplicaciones que no sean web, los desarrolladores deben usar el host
genérico basado en HostBuilder.
Host web de ASP.NET Core
25/05/2018 • 33 minutes to read • Edit Online
Configuración de un host
ASP.NET Core 2.x
ASP.NET Core 1.x
Cree un host con una instancia de WebHostBuilder. Normalmente, esto se realiza en el punto de entrada de la
aplicación, el método Main . En las plantillas de proyecto, Main se encuentra en Program.cs. Los archivos
Program.cs estándar llaman a CreateDefaultBuilder para empezar a configurar un host:
NOTE
Como alternativa al uso del método estático CreateDefaultBuilder , crear un host de WebHostBuilder es un enfoque
compatible con ASP.NET Core 2.x. Para más información, vea la pestaña ASP.NET Core 1.x.
Al configurar un host, se pueden proporcionar los métodos Configure y ConfigureServices. Si se especifica una
clase Startup , debe definir un método Configure . Para obtener más información, vea Application Startup in
ASP.NET Core (Inicio de la aplicación en ASP.NET Core). Varias llamadas a ConfigureServices se anexan entre sí.
Varias llamadas a Configure o UseStartup en el WebHostBuilder reemplazan la configuración anterior.
WebHost.CreateDefaultBuilder(args)
.CaptureStartupErrors(true)
WebHost.CreateDefaultBuilder(args)
.UseContentRoot("c:\\<content-root>")
Errores detallados
Determina si se deben capturar los errores detallados.
Clave: detailedErrors
Tipo: bool ( true o 1 )
Valor predeterminado: false
Establecer mediante: UseSetting
Variable de entorno: ASPNETCORE_DETAILEDERRORS
Cuando se habilita (o cuando el entorno está establecido en Development ), la aplicación captura excepciones
detalladas.
ASP.NET Core 2.x
ASP.NET Core 1.x
WebHost.CreateDefaultBuilder(args)
.UseSetting(WebHostDefaults.DetailedErrorsKey, "true")
Entorno
Establece el entorno de la aplicación.
Clave: environment
Tipo: cadena
Valor predeterminado: producción
Establecer mediante: UseEnvironment
Variable de entorno: ASPNETCORE_ENVIRONMENT
El entorno se puede establecer en cualquier valor. Los valores definidos por el marco son Development , Staging
y Production . Los valores no distinguen mayúsculas de minúsculas. De forma predeterminada, el entorno se lee
desde la variable de entorno ASPNETCORE_ENVIRONMENT . Cuando se usa Visual Studio, las variables de entorno se
pueden establecer en el archivo launchSettings.json. Para obtener más información, consulte Uso de varios
entornos.
ASP.NET Core 2.x
ASP.NET Core 1.x
WebHost.CreateDefaultBuilder(args)
.UseEnvironment(EnvironmentName.Development)
WebHost.CreateDefaultBuilder(args)
.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "assembly1;assembly2")
WebHost.CreateDefaultBuilder(args)
.PreferHostingUrls(false)
WebHost.CreateDefaultBuilder(args)
.UseSetting(WebHostDefaults.PreventHostingStartupKey, "true")
WebHost.CreateDefaultBuilder(args)
.UseUrls("http://*:5000;http://localhost:5001;https://hostname:5002")
Kestrel tiene su propia API de configuración de punto de conexión. Para más información, vea Kestrel web server
implementation in ASP.NET Core (Implementación del servidor web de Kestrel en ASP.NET Core).
Tiempo de espera de apagado
Especifica la cantidad de tiempo que se espera hasta el cierre del host de web.
Clave: shutdownTimeoutSeconds
Tipo: int
Valor predeterminado: 5
Establecer mediante: UseShutdownTimeout
Variable de entorno: ASPNETCORE_SHUTDOWNTIMEOUTSECONDS
Aunque la clave acepta un int con UseSetting(por ejemplo,
.UseSetting(WebHostDefaults.ShutdownTimeoutKey, "10") ), el método de extensión UseShutdownTimeout toma
TimeSpan. Esta característica es nueva en ASP.NET Core 2.0.
Durante el período de tiempo de espera, el hospedaje:
Activa IApplicationLifetime.ApplicationStopping.
Trata de detener los servicios hospedados y registra cualquier error que se produzca en los servicios que no
se puedan detener.
Si el período de tiempo de espera expira antes de que todos los servicios hospedados se hayan detenido, los
servicios activos que queden se detendrán cuando se cierre la aplicación. Los servicios se detienen aun cuando
no hayan terminado de procesarse. Si los servicios requieren más tiempo para detenerse, aumente el valor de
tiempo de espera.
ASP.NET Core 2.x
ASP.NET Core 1.x
WebHost.CreateDefaultBuilder(args)
.UseShutdownTimeout(TimeSpan.FromSeconds(10))
Ensamblado de inicio
Determina el ensamblado en el que se va a buscar la clase Startup .
Clave: startupAssembly
Tipo: cadena
Valor predeterminado: el ensamblado de la aplicación
Establecer mediante: UseStartup
Variable de entorno: ASPNETCORE_STARTUPASSEMBLY
Se puede hacer referencia al ensamblado por su nombre ( string ) o su tipo ( TStartup ). Si se llama a varios
métodos UseStartup , la última llamada tiene prioridad.
ASP.NET Core 2.x
ASP.NET Core 1.x
WebHost.CreateDefaultBuilder(args)
.UseStartup("StartupAssemblyName")
WebHost.CreateDefaultBuilder(args)
.UseStartup<TStartup>()
Raíz web
Establece la ruta de acceso relativa a los recursos estáticos de la aplicación.
Clave: webroot
Tipo: cadena
Valor predeterminado: si no se especifica, el valor predeterminado es "(Raíz de contenido)/wwwroot", si existe
la ruta de acceso. Si la ruta de acceso no existe, se utiliza un proveedor de archivos no-op.
Establecer mediante: UseWebRoot
Variable de entorno: ASPNETCORE_WEBROOT
ASP.NET Core 2.x
ASP.NET Core 1.x
WebHost.CreateDefaultBuilder(args)
.UseWebRoot("public")
Invalidación de la configuración
Use Configuración para configurar el host. En el ejemplo siguiente, la configuración del host se especifica de
forma opcional en un archivo hosting.json. Cualquier configuración que se carga desde el archivo hosting.json
puede reemplazarse por argumentos de línea de comandos. La configuración generada (en config ) se utiliza
para configurar el host con UseConfiguration .
ASP.NET Core 2.x
ASP.NET Core 1.x
hosting.json:
{
urls: "http://*:5005"
}
Invalidar la configuración proporcionada por UseUrls primero con la configuración de hosting.json y segundo
con la configuración del argumento de línea de comandos:
return WebHost.CreateDefaultBuilder(args)
.UseUrls("http://*:5000")
.UseConfiguration(config)
.Configure(app =>
{
app.Run(context =>
context.Response.WriteAsync("Hello, World!"));
})
.Build();
}
}
NOTE
El método de extensión UseConfiguration no es capaz de analizar actualmente una sección de configuración devuelta por
GetSection (por ejemplo, .UseConfiguration(Configuration.GetSection("section")) . El método GetSection filtra
las claves de configuración a la sección solicitada, pero deja el nombre de sección en las claves (por ejemplo, section:urls
, section:environment ). El método UseConfiguration espera que las claves coincidan con las claves WebHostBuilder
(por ejemplo, urls , environment ). La presencia del nombre de sección en las claves evita que los valores de la sección
configuren el host. Este problema se corregirá en una versión futura. Para obtener más información y soluciones
alternativas, consulte Passing configuration section into WebHostBuilder.UseConfiguration uses full keys (Pasar la sección de
configuración a WebHostBuilder.UseConfiguration usa claves completas).
Para especificar el host que se ejecuta en una dirección URL determinada, se puede pasar el valor deseado desde
un símbolo del sistema al ejecutar dotnet run. El argumento de línea de comandos reemplaza el valor urls del
archivo hosting.json, y el servidor escucha en el puerto 8080:
host.Run();
Start
Ejecute el host de manera que se evite un bloqueo mediante una llamada a su método Start :
using (host)
{
host.Start();
Console.ReadLine();
}
Si se pasa una lista de direcciones URL al método Start , la escucha se produce en las direcciones URL
especificadas:
using (host)
{
Console.ReadLine();
}
La aplicación puede inicializar un nuevo host usando los valores preconfigurados de CreateDefaultBuilder
mediante un método práctico estático. Estos métodos inician el servidor sin la salida de la consola y con
WaitForShutdown esperando una interrupción (Ctrl-C/SIGINT o SIGTERM ):
Start(RequestDelegate app)
Start con RequestDelegate :
Haga una solicitud en el explorador a http://localhost:5000 para recibir la respuesta "Hello World!"
WaitForShutdown se bloquea hasta que se emite un salto ( Ctrl-C/SIGINT o SIGTERM ). La aplicación muestra el
mensaje Console.WriteLine y espera a que se pulse una tecla para salir.
Start(url de cadena, RequestDelegate app)
Start con una dirección URL y RequestDelegate :
Produce el mismo resultado que Start(RequestDelegate app), excepto que la aplicación responde en
http://localhost:8080 .
Start(Action<IRouteBuilder> routeBuilder)
Use una instancia de IRouteBuilder (Microsoft.AspNetCore.Routing) para usar el middleware de enrutamiento:
SOLICITUD RESPUESTA
WaitForShutdown se bloquea hasta que se emite un salto (Ctrl-C/SIGINT o SIGTERM ). La aplicación muestra el
mensaje Console.WriteLine y espera a que se pulse una tecla para salir.
Start(string url, Action<IRouteBuilder> routeBuilder)
Use una dirección URL y una instancia de IRouteBuilder :
using (var host = WebHost.Start("http://localhost:8080", router => router
.MapGet("hello/{name}", (req, res, data) =>
res.WriteAsync($"Hello, {data.Values["name"]}!"))
.MapGet("buenosdias/{name}", (req, res, data) =>
res.WriteAsync($"Buenos dias, {data.Values["name"]}!"))
.MapGet("throw/{message?}", (req, res, data) =>
throw new Exception((string)data.Values["message"] ?? "Uh oh!"))
.MapGet("{greeting}/{name}", (req, res, data) =>
res.WriteAsync($"{data.Values["greeting"]}, {data.Values["name"]}!"))
.MapGet("", (req, res, data) => res.WriteAsync("Hello, World!"))))
{
Console.WriteLine("Use Ctrl-C to shut down the host...");
host.WaitForShutdown();
}
Haga una solicitud en el explorador a http://localhost:5000 para recibir la respuesta "Hello World!"
WaitForShutdown se bloquea hasta que se emite un salto ( Ctrl-C/SIGINT o SIGTERM ). La aplicación muestra el
mensaje Console.WriteLine y espera a que se pulse una tecla para salir.
StartWith(string url, Action<IApplicationBuilder> app)
Proporcione una dirección URL y un delegado para configurar IApplicationBuilder :
Interfaz IHostingEnvironment
La interfaz IHostingEnvironment proporciona información sobre el entorno de hospedaje web de la aplicación.
Use inserción de constructores para obtener IHostingEnvironment a fin de utilizar sus propiedades y métodos de
extensión:
Puede utilizarse un enfoque convencional para configurar la aplicación en el inicio según el entorno. Como
alternativa, inserte IHostingEnvironment en el constructor Startup para su uso en ConfigureServices :
NOTE
Además del método de extensión IsDevelopment , IHostingEnvironment ofrece los métodos IsStaging ,
IsProduction y IsEnvironment(string environmentName) . Para más información, vea Usar varios entornos.
Interfaz IApplicationLifetime
IApplicationLifetime permite actividades posteriores al inicio y apagado. Hay tres propiedades en la interfaz que
son tokens de cancelación usados para registrar métodos Action que definen los eventos de inicio y apagado.
StopApplication solicita la terminación de la aplicación. La siguiente clase usa StopApplication para cerrar de
forma estable una aplicación cuando se llama al método Shutdown de esa clase:
WebHost.CreateDefaultBuilder(args)
.UseDefaultServiceProvider((context, options) => {
options.ValidateScopes = true;
})
services.AddSingleton<IStartup, Startup>();
Esto ocurre porque applicationName(ApplicationKey) (el ensamblado actual) es necesario para buscar
HostingStartupAttributes . Si la aplicación inserta manualmente IStartup en el contenedor de inserción de
dependencias, agregue la siguiente llamada a WebHostBuilder con el nombre de ensamblado especificado:
WebHost.CreateDefaultBuilder(args)
.UseSetting("applicationName", "<Assembly Name>")
...
WebHost.CreateDefaultBuilder(args)
.Configure(_ => { })
...
NOTA: Esto solo es necesario con la versión ASP.NET Core 2.0 y únicamente cuando la aplicación no llama a
UseStartup o Configure .
Recursos adicionales
Hospedaje en Windows con IIS
Hospedaje en Linux con Nginx
Hospedaje en Linux con Apache
Hospedaje en un servicio de Windows
Host genérico de .NET
04/06/2018 • 18 minutes to read • Edit Online
Introducción
La biblioteca de host genérico está disponible en el espacio de nombres de Microsoft.Extensions.Hosting y la
proporciona el paquete Microsoft.Extensions.Hosting NuGet. El paquete Microsoft.Extensions.Hosting está
incluido en el metapaquete Microsoft.AspNetCore.App.
IHostedService es el punto de entrada para la ejecución de código. Cada implementación de IHostedService se
ejecuta en el orden de registro del servicio en ConfigureServices. Se llama a StartAsync en cada IHostedService
cuando se inicia el host, y se llama a StopAsync en orden inverso del registro cuando el host se cierra de forma
estable.
Configuración de un host
IHostBuilder es el componente principal que usan las bibliotecas y aplicaciones para inicializar, compilar y ejecutar
el host:
public static async Task Main(string[] args)
{
var host = new HostBuilder()
.Build();
await host.RunAsync();
}
Configuración de host
HostBuilder se basa en los siguientes métodos para establecer los valores de configuración del host:
Generador de configuración
Configuración del método de extensión
Generador de configuración
La configuración del generador de host se crea mediante una llamada a ConfigureHostConfiguration en la
implementación de IHostBuilder. ConfigureHostConfiguration usa IConfigurationBuilder para crear
IConfiguration para el host. El generador de configuración inicializa IHostingEnvironment para su uso en el
proceso de compilación de la aplicación. Se puede llamar varias veces a ConfigureHostConfiguration con
resultados de suma. El host usa cualquier opción que establece un valor en último lugar.
hostsettings.json:
{
"environment": "Development"
}
NOTE
El método de extensión AddConfiguration no es capaz de analizar actualmente una sección de configuración devuelta por
GetSection (por ejemplo, .AddConfiguration(Configuration.GetSection("section")) ). El método GetSection filtra las
claves de configuración a la sección solicitada, pero deja el nombre de sección en las claves (por ejemplo,
section:environment ). El método AddConfiguration espera que las claves coincidan con las claves HostBuilder (por
ejemplo, environment ). La presencia del nombre de sección en las claves evita que los valores de la sección configuren el
host. Este problema se corregirá en una versión futura. Para obtener más información y soluciones alternativas, consulte
Passing configuration section into WebHostBuilder.UseConfiguration uses full keys (Pasar la sección de configuración a
WebHostBuilder.UseConfiguration usa claves completas).
Entorno
Establece el entorno de la aplicación.
Clave: environment
Tipo: cadena
Valor predeterminado: producción
Establecer mediante: UseEnvironment
Variable de entorno: ASPNETCORE_ENVIRONMENT
El entorno se puede establecer en cualquier valor. Los valores definidos por el marco son Development , Staging y
Production . Los valores no distinguen mayúsculas de minúsculas. De forma predeterminada, el entorno se lee
desde la variable de entorno ASPNETCORE_ENVIRONMENT . Cuando se usa Visual Studio, las variables de entorno se
pueden establecer en el archivo launchSettings.json. Para obtener más información, consulte Uso de varios
entornos.
ConfigureAppConfiguration
La configuración del generador de la aplicación se crea mediante una llamada a ConfigureAppConfiguration en la
implementación de IHostBuilder. ConfigureAppConfiguration usa un IConfigurationBuilder para crear un
IConfiguration para la aplicación. Se puede llamar varias veces a ConfigureAppConfiguration con resultados de
suma. La aplicación usa cualquier opción que establece un valor en último lugar. La configuración creada por
ConfigureAppConfiguration está disponible en HostBuilderContext.Configuration para las operaciones posteriores
y en Services.
Configuración de aplicación de ejemplo con ConfigureAppConfiguration :
appsettings.json:
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
appsettings.Development.json:
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}
appsettings.Production.json:
{
"Logging": {
"LogLevel": {
"Default": "Error",
"System": "Information",
"Microsoft": "Information"
}
}
}
NOTE
El método de extensión AddConfiguration no es capaz de analizar actualmente una sección de configuración devuelta por
GetSection (por ejemplo, .AddConfiguration(Configuration.GetSection("section")) ). El método GetSection filtra las
claves de configuración a la sección solicitada, pero deja el nombre de sección en las claves (por ejemplo,
section:Logging:LogLevel:Default ). El método AddConfiguration espera una coincidencia exacta con las claves de
configuración (por ejemplo, Logging:LogLevel:Default ). La presencia del nombre de sección en las claves evita que los
valores de la sección configuren la aplicación. Este problema se corregirá en una versión futura. Para obtener más
información y soluciones alternativas, consulte Passing configuration section into WebHostBuilder.UseConfiguration uses full
keys (Pasar la sección de configuración a WebHostBuilder.UseConfiguration usa claves completas).
ConfigureServices
ConfigureServices agrega los servicios al contenedor de inserción de dependencias de la aplicación. Se puede
llamar varias veces a ConfigureServices con resultados de suma.
Un servicio hospedado es una clase con lógica de tarea en segundo plano que implementa la interfaz
IHostedService. Para más información, consulte el tema Tareas en segundo plano con servicios hospedados.
La aplicación de ejemplo usa el método de extensión AddHostedService para agregar un servicio para eventos de
duración, LifetimeEventsHostedService , y una tarea en segundo plano programada, TimedHostedService , a la
aplicación:
var host = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddLogging();
services.AddHostedService<LifetimeEventsHostedService>();
services.AddHostedService<TimedHostedService>();
})
ConfigureLogging
ConfigureLogging agrega un delegado para configurar el ILoggingBuilder proporcionado. Se puede llamar varias
veces a ConfigureLogging con resultados de suma.
UseConsoleLifetime
UseConsoleLifetime escucha Ctrl+C /SIGINT o SIGTERM y llama a StopApplication para iniciar el proceso de
cierre. UseConsoleLifetime desbloquea extensiones como RunAsync y WaitForShutdownAsync. ConsoleLifetime
ya está registrado previamente como la implementación de duración predeterminada. Se usa la última duración
registrada.
namespace GenericHostSample
{
internal class ServiceContainer
{
}
}
namespace GenericHostSample
{
internal class ServiceContainerFactory : IServiceProviderFactory<MyContainer>
{
public ServiceContainer CreateBuilder(IServiceCollection services)
{
return new ServiceContainer();
}
Extensibilidad
La extensibilidad de host se realiza con métodos de extensión en IHostBuilder . El ejemplo siguiente muestra
cómo un método de extensión extiende una implementación de IHostBuilder con RabbitMQ. El método de
extensión (en otra parte de la aplicación) registra un IHostedService de RabbitMQ:
await host.StartAsync();
host.Run();
}
}
RunAsync
RunAsync inicia la aplicación y devuelve Task , que se completa cuando se desencadena el token de cancelación o
el cierre:
await host.RunAsync();
}
}
RunConsoleAsync
RunConsoleAsync habilita la compatibilidad de la consola, compila e inicia el host y espera a que se cierre Ctrl+C
/SIGINT o SIGTERM.
await hostBuilder.RunConsoleAsync();
}
}
Start y StopAsync
Start inicia el host de forma sincrónica.
StopAsync(TimeSpan) intenta detener el host en el tiempo de espera proporcionado.
public class Program
{
public static async Task Main(string[] args)
{
var host = new HostBuilder()
.Build();
using (host)
{
host.Start();
await host.StopAsync(TimeSpan.FromSeconds(5));
}
}
}
StartAsync y StopAsync
StartAsync inicia la aplicación.
StopAsync detiene la aplicación.
using (host)
{
await host.StartAsync();
await host.StopAsync();
}
}
}
WaitForShutdown
WaitForShutdown se desencadena mediante IHostLifetime, como ConsoleLifetime (escucha Ctrl+C /SIGINT o
SIGTERM ). WaitForShutdown llama a StopAsync.
using (host)
{
host.Start();
host.WaitForShutdown();
}
}
}
WaitForShutdownAsync
WaitForShutdownAsync devuelve Task , que se completa cuando se desencadena el cierre a través del token
determinado y llama a StopAsync.
public class Program
{
public static async Task Main(string[] args)
{
var host = new HostBuilder()
.Build();
using (host)
{
await host.StartAsync();
await host.WaitForShutdownAsync();
}
}
}
Control externo
El control externo del host se puede lograr mediante métodos a los que se pueda llamar de forma externa:
public Program()
{
_host = new HostBuilder()
.Build();
}
IHostLifetime.WaitForStartAsync se llama al inicio de StartAsync, que espera hasta que se complete antes de
continuar. Esto se puede usar para retrasar el inicio hasta que lo indique un evento externo.
Interfaz IHostingEnvironment
IHostingEnvironment proporciona información sobre el entorno de hospedaje de la aplicación. Use inserción de
constructores para obtener IHostingEnvironment a fin de utilizar sus propiedades y métodos de extensión:
public class MyClass
{
private readonly IHostingEnvironment _env;
Interfaz IApplicationLifetime
IApplicationLifetime permite actividades posteriores al inicio y cierre, incluidas las solicitudes de cierre estable.
Hay tres propiedades en la interfaz que son tokens de cancelación usados para registrar métodos Action que
definen los eventos de inicio y apagado.
Inserción de constructor del servicio IApplicationLifetime en cualquier clase. La aplicación de ejemplo utiliza la
inserción de constructor en una clase LifetimeEventsHostedService (una implementación de IHostedService ) para
registrar los eventos.
LifetimeEventsHostedService.cs:
internal class LifetimeEventsHostedService : IHostedService
{
private readonly ILogger _logger;
private readonly IApplicationLifetime _appLifetime;
public LifetimeEventsHostedService(
ILogger<LifetimeEventsHostedService> logger, IApplicationLifetime appLifetime)
{
_logger = logger;
_appLifetime = appLifetime;
}
return Task.CompletedTask;
}
StopApplication solicita la terminación de la aplicación. La siguiente clase usa StopApplication para cerrar de
forma estable una aplicación cuando se llama al método Shutdown de esa clase:
public class MyClass
{
private readonly IApplicationLifetime _appLifetime;
Recursos adicionales
Tareas en segundo plano con servicios hospedados
Ejemplos de hospedaje de repositorios en GitHub
Tareas en segundo plano con servicios hospedados
en ASP.NET Core
31/05/2018 • 7 minutes to read • Edit Online
Interfaz IHostedService
Los servicios hospedados implementan la interfaz IHostedService. Esta interfaz define dos métodos para los
objetos administrados por el host:
StartAsync(CancellationToken): se llama después de que el servidor se haya iniciado y
IApplicationLifetime.ApplicationStarted se haya activado. StartAsync contiene la lógica para iniciar la tarea
en segundo plano.
StopAsync(CancellationToken): se activa cuando el host está realizando un cierre estable. StopAsync
contiene la lógica para finalizar la tarea en segundo plano y desechar los recursos no administrados. Si la
aplicación se cierra inesperadamente (por ejemplo, porque se produzca un error en el proceso de la
aplicación), puede que no sea posible llamar a StopAsync .
El servicio hospedado es un singleton que se activa una vez en el inicio de la aplicación y se cierra de manera
estable cuando dicha aplicación se cierra. Si IDisposable está implementada, se pueden desechar recursos cuando
se deseche el contenedor de servicios. Si se produce un error durante la ejecución de una tarea en segundo plano,
hay que llamar a Dispose , aun cuando no se haya llamado a StopAsync .
return Task.CompletedTask;
}
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
services.AddHostedService<TimedHostedService>();
El servicio hospedado crea un ámbito con objeto de resolver el servicio de tareas en segundo plano con ámbito
para llamar a su método DoWork :
internal class ConsumeScopedServiceHostedService : IHostedService
{
private readonly ILogger _logger;
DoWork();
return Task.CompletedTask;
}
scopedProcessingService.DoWork();
}
}
return Task.CompletedTask;
}
}
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
_workItems.Enqueue(workItem);
_signal.Release();
}
return workItem;
}
}
En QueueHostedService , las tareas en segundo plano ( workItem ) que están en cola se quitan de ella y se ejecutan:
public class QueuedHostedService : IHostedService
{
private CancellationTokenSource _shutdown =
new CancellationTokenSource();
private Task _backgroundTask;
private readonly ILogger _logger;
_backgroundTask = Task.Run(BackgroundProceessing);
return Task.CompletedTask;
}
try
{
await workItem(_shutdown.Token);
}
catch (Exception ex)
{
_logger.LogError(ex,
$"Error occurred executing {nameof(workItem)}.");
}
}
}
_shutdown.Cancel();
return Task.WhenAny(_backgroundTask,
Task.Delay(Timeout.Infinite, cancellationToken));
}
}
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
Cuando se hace clic en el botón Agregar tarea en la página de índice, se ejecuta el método OnPostAddTask . Se
llama a QueueBackgroundWorkItem para poner en cola el elemento de trabajo:
_logger.LogInformation(
$"Queued Background Task {guid} is complete. 3/3");
});
return RedirectToPage();
}
Recursos adicionales
Implementar tareas en segundo plano en microservicios con IHostedService y la clase BackgroundService
System.Threading.Timer
Estado de sesión y aplicación en ASP.NET Core
19/06/2018 • 26 minutes to read • Edit Online
Estado de sesión
El estado de sesión es una característica de ASP.NET Core que se puede usar para guardar y almacenar datos de
usuario mientras el usuario explora la aplicación web. El estado de sesión, que está compuesto por un diccionario
o tabla hash en el servidor, conserva los datos de todas las solicitudes desde un explorador. Los datos de sesión
se guardan en una copia de seguridad en una memoria caché.
Para mantener el estado de sesión, ASP.NET Core proporciona al cliente una cookie que contiene el identificador
de sesión, que se envía al servidor con cada solicitud. El servidor utiliza el identificador de sesión para capturar
los datos de sesión. Dado que la cookie de sesión es específica del explorador, no es posible compartir las
sesiones entre los exploradores. Las cookies de sesión se eliminan cuando finaliza la sesión del explorador. Si se
recibe una cookie de una sesión que ha expirado, se crea una nueva sesión que usa la misma cookie de sesión.
El servidor conserva una sesión durante un tiempo limitado después de la última solicitud. Se puede especificar
un tiempo de espera de sesión o usar el valor predeterminado de 20 minutos. El estado de sesión es ideal para
almacenar datos de usuario que son específicos de una sesión determinada, pero que no necesitan conservarse
de forma permanente. Los datos se eliminan de la memoria auxiliar cuando se llama a Session.Clear o cuando
la sesión expira en el almacén de datos. El servidor no sabe cuándo se cierra el explorador o cuándo se elimina la
cookie de sesión.
WARNING
No almacene datos confidenciales en la sesión, dado que el cliente podría no cerrar el explorador ni borrar la cookie de
sesión (además, algunos exploradores mantienen las cookies de sesión activas en distintas ventanas). También es posible
que una sesión no esté restringida a un único usuario y que el siguiente usuario continúe con la misma sesión.
El proveedor de sesión en memoria almacena datos de la sesión en el servidor local. Si tiene previsto ejecutar la
aplicación web en una granja de servidores, debe usar sesiones permanentes para asociar cada sesión a un
servidor específico. El valor predeterminado de la plataforma Windows Azure Web Sites es usar sesiones
permanentes (enrutamiento de solicitud de aplicación o ARR ). Pero las sesiones permanentes pueden afectar a la
escalabilidad y complicar la actualización de las aplicaciones web. Una mejor opción consiste en usar las
memorias caché distribuidas de Redis o SQL Server, que no requieren sesiones permanentes. Para más
información, vea Working with a Distributed Cache (Trabajar con una memoria caché distribuida). Para obtener
más información sobre cómo configurar los proveedores de servicios, consulte Configuración de sesión más
adelante en este artículo.
TempData
ASP.NET Core MVC expone la propiedad TempData en un controlador. Esta propiedad almacena datos hasta que
se leen. Los métodos Keep y Peek se pueden usar para examinar los datos sin que se eliminen. TempData es
particularmente útil para el redireccionamiento cuando se necesitan los datos de más de una única solicitud. Los
proveedores de TempData implementan TempData mediante, por ejemplo, cookies o estado de sesión.
Proveedores de TempData
ASP.NET Core 2.x
ASP.NET Core 1.x
En ASP.NET Core 2.0 y versiones posteriores, el proveedor TempData basado en cookies se utiliza de forma
predeterminada para almacenar TempData en cookies.
Los datos de cookie se cifran mediante IDataProtector, codificado con Base64UrlTextEncoder, y después se
fragmentan. Como la cookie está fragmentada, no se aplica el límite de tamaño único de cookie que se encuentra
en ASP.NET Core 1.x. Los datos de cookie no se comprimen porque la compresión de datos cifrados puede
provocar problemas de seguridad como los ataques CRIME y BREACH. Para obtener más información sobre el
proveedor TempData basado en cookies, consulte CookieTempDataProvider.
Elegir un proveedor TempData
Elegir un proveedor TempData implica una serie de consideraciones:
1. ¿La aplicación ya usa el estado de sesión para otros fines? Si es así, la utilización del proveedor TempData de
estado de sesión no tiene costo adicional para la aplicación (excepto en el tamaño de los datos).
2. ¿La aplicación usa TempData con moderación, solo para cantidades relativamente pequeñas de datos (hasta
500 bytes)? Si es así, el proveedor TempData de cookies agregará un pequeño costo a cada solicitud que
transporta TempData. De lo contrario, el proveedor TempData de estado de sesión puede ser beneficioso para
evitar que una gran cantidad de datos hagan un recorrido de ida y vuelta en cada solicitud hasta que se
consuma TempData.
3. ¿La aplicación se ejecuta en una granja de servidores web (varios servidores)? En caso afirmativo, no es
necesario realizar ninguna otra configuración para usar el proveedor TempData de cookies.
NOTE
La mayoría de los clientes de web (por ejemplo, los exploradores web) aplican límites en el tamaño máximo de cada cookie,
el número total de cookies o ambos. Por lo tanto, cuando use el proveedor TempData de cookies, compruebe que la
aplicación no supera esos límites. Valore el tamaño total de los datos, teniendo en cuenta las sobrecargas de cifrado y
fragmentación.
services.AddSession();
}
IMPORTANT
Si el destino es .NET Framework y usa el proveedor basado en sesión, agregue el paquete NuGet
Microsoft.AspNetCore.Session a su proyecto.
Cadenas de consulta
Puede pasar una cantidad limitada de datos de una solicitud a otra si agrega los datos a la cadena de consulta de
la solicitud nueva. Esto es útil para capturar el estado de una forma persistente que permita que los vínculos con
estado insertado se compartan a través del correo electrónico o las redes sociales. Pero, por esta razón, nunca
deben usarse las cadenas de consulta para datos confidenciales. Además de que se pueden compartir con
facilidad, la inclusión de los datos en cadenas de consulta puede propiciar ataques de falsificación de solicitud
entre sitios (CSRF ), cuya intención es engañar a los usuarios para que visiten sitios malintencionados mientras
están autenticados. A continuación, los atacantes pueden robar los datos de usuario de la aplicación o realizar
acciones malintencionadas en nombre del usuario. Cualquier estado de sesión o aplicación conservado debe
protegerse contra los ataques CSRF. Para más información sobre los ataques CSRF, vea Prevent Cross-Site
Request Forgery (XSRF/CSRF ) Attacks (Evitar los ataques de falsificación de solicitud entre sitios [XSRF/CSRF ]).
Cookies
Las cookies proporcionan una manera de almacenar datos específicos del usuario en aplicaciones web. Dado que
las cookies se envían con cada solicitud, su tamaño debe reducirse al mínimo. Lo ideal es que en cada cookie se
almacene un solo identificador y que los datos reales se almacenen en el servidor. La mayoría de los
exploradores restringen el tamaño de las cookies a 4096 bytes. Además, solo hay disponible un número limitado
de cookies para cada dominio.
Como las cookies están expuestas a alteraciones, deben validarse en el servidor. Aunque la duración de la cookie
en un cliente está sujeta a un plazo de expiración y a la intervención del usuario, generalmente son la forma más
duradera de persistencia de datos en el cliente.
Las cookies suelen utilizarse para personalizar el contenido ofrecido a un usuario conocido. Puesto que en la
mayoría de los casos el usuario tan solo se identifica y no se autentica, normalmente se puede proteger una
cookie si se almacena el nombre de usuario, el nombre de cuenta o un identificador de usuario único (por
ejemplo, un GUID ) en la cookie. A continuación, se puede usar la cookie para tener acceso a la infraestructura de
personalización de usuarios de un sitio.
HttpContext.Items
La colección Items es un buen lugar para almacenar los datos que solo son necesarios para el procesamiento de
una solicitud determinada. El contenido de la colección se descarta después de cada solicitud. Se aconseja utilizar
la colección Items como forma de comunicación entre los componentes o el middleware cuando operan en
distintos puntos en el tiempo durante una solicitud y no pueden pasarse parámetros de forma directa. Para más
información, vea Trabajar con HttpContext.Items, más adelante en este artículo.
instancias y claves
El almacenamiento en caché es una manera eficaz de almacenar y recuperar datos. Puede controlar la duración
de los elementos almacenados en caché en función del tiempo y otras consideraciones. Obtenga más
información sobre cómo almacenar en caché.
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using System;
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.Cookie.HttpOnly = true;
});
}
Puede hacer referencia a la sesión de HttpContext una vez que se ha instalado y configurado.
Si se intenta acceder a Session antes de haber llamado a UseSession , se produce la excepción
InvalidOperationException: Session has not been configured for this application or request .
Si intenta crear una nueva Session (es decir, no se ha creado ninguna cookie de sesión) después de que ya haya
empezado a escribir en el flujo Response , se produce la excepción
InvalidOperationException: The session cannot be established after the response has started . La excepción
puede encontrarse en el registro de servidor web; no se mostrará en el explorador.
Carga de sesión de forma asincrónica
El proveedor de sesión predeterminado de ASP.NET Core carga asincrónicamente el registro de sesión desde el
almacén subyacente IDistributedCache solo si el método ISession.LoadAsync se llama explícitamente antes que
los métodos TryGetValue , Set o Remove . Si primero no se llama a LoadAsync , el registro de sesión subyacente
se carga de forma sincrónica, lo que podría tener consecuencias sobre la capacidad de la aplicación para
escalarse.
Para que las aplicaciones impongan este patrón, ajuste las implementaciones de DistributedSessionStore y
DistributedSession con versiones que produzcan una excepción si el método LoadAsync no se llama antes que
TryGetValue , Set o Remove . Registre las versiones ajustadas en el contenedor de servicios.
Detalles de la implementación
La sesión utiliza una cookie para realizar el seguimiento de las solicitudes emitidas por un solo explorador e
identificarlas. De manera predeterminada, esta cookie se denomina ".AspNet.Session" y utiliza una ruta de acceso
de "/". Dado que el valor predeterminado de la cookie no especifica un dominio, no estará disponible para el
script de cliente en la página (porque CookieHttpOnly tiene como valor predeterminado true ).
Para reemplazar los valores predeterminados de la sesión, use SessionOptions :
ASP.NET Core 2.x
ASP.NET Core 1.x
services.AddSession(options =>
{
options.Cookie.Name = ".AdventureWorks.Session";
options.IdleTimeout = TimeSpan.FromSeconds(10);
});
}
El servidor usa la propiedad IdleTimeout para determinar el tiempo que una sesión puede estar inactiva antes
de que se abandone su contenido. Esta propiedad es independiente de la expiración de la cookie. Cada solicitud
que se pasa a través del middleware de sesión (leídas o escritas) restablece el tiempo de espera.
Dado que Session no realiza bloqueo, si dos solicitudes intentan modificar el contenido de la sesión, la última
de ellas reemplaza a la primera. Session se implementa como una sesión coherente, lo que significa que todo el
contenido se almacena junto. Dos solicitudes que estén modificando distintas partes de la sesión (claves
diferentes) aún podrían verse afectadas entre sí.
Establecer y obtener valores de Session
El acceso a Session se realiza desde una vista o página de Razor con Context.Session :
@using Microsoft.AspNetCore.Http
Session Value = @Context.Session.GetString("_Name")
El acceso a Session se realiza desde un controlador o clase PageModel con HttpContext.Session : Esta propiedad
es una implementación de ISession.
En el ejemplo siguiente se muestra cómo establecer y obtener un valor int y una cadena:
public class HomeController : Controller
{
const string SessionKeyName = "_Name";
const string SessionKeyYearsMember = "_YearsMember";
const string SessionKeyDate = "_Date";
Si agrega los siguientes métodos de extensión, puede establecer y obtener objetos serializables en sesión:
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
Si el middleware se va a usar en una única aplicación, se aceptan claves string . Pero el middleware que se
compartirá entre aplicaciones debe usar claves de objeto únicas para evitar cualquier posibilidad de colisión
entre claves. Si está desarrollando middleware que debe trabajar en varias aplicaciones, utilice una clave de
objeto única definida en la clase de middleware, tal y como se muestra a continuación:
Otro código puede tener acceso al valor almacenado en HttpContext.Items con la clave que expone la clase de
middleware:
Este enfoque también tiene la ventaja de eliminar la repetición de "cadenas mágicas" en varios lugares en el
código.
Kestrel
Kestrel es el servidor web predeterminado que se incluye en las plantillas de proyecto de ASP.NET Core.
ASP.NET Core 2.x
ASP.NET Core 1.x
Se puede usar Kestrel por sí solo o con un servidor proxy inverso, como IIS, Nginx o Apache. Un servidor proxy
inverso recibe las solicitudes HTTP de Internet y las reenvía a Kestrel después de un control preliminar.
Cualquiera de las configuraciones—con o sin un servidor proxy inverso—es una configuración de hospedaje
válida y admitida para ASP.NET 2.0 o aplicaciones posteriores. Para más información, vea When to use Kestrel
with a reverse proxy (Cuándo se debe usar Kestrel con un proxy inverso).
No se puede usar IIS, Nginx o Apache sin Kestrel ni ninguna implementación de servidor personalizado.
ASP.NET Core se ha diseñado para ejecutarse en su propio proceso de modo que pueda comportarse de forma
coherente entre varias plataformas. IIS, Nginx y Apache dictan su propio procedimiento y entorno de inicio. Para
usar estas tecnologías de servidor directamente, ASP.NET Core tendría que adaptarse a los requisitos de cada
servidor. El uso de una implementación de servidor web como Kestrel proporciona a ASP.NET Core el control
sobre el entorno y el proceso de inicio cuando se hospedan en tecnologías de servidor diferentes.
IIS con Kestrel
Al usar IIS o IIS Express como proxy inverso para ASP.NET Core, la aplicación ASP.NET Core se ejecuta en un
proceso independiente del proceso de trabajo de IIS. En el proceso IIS, el módulo de ASP.NET Core coordina la
relación de proxy inverso. Las funciones principales del módulo de ASP.NET Core consisten en iniciar la
aplicación ASP.NET Core, reiniciarla cuando se bloquea y reenviar el tráfico HTTP a la aplicación. Para más
información, vea ASP.NET Core Module (Módulo de ASP.NET Core).
Nginx con Kestrel
Para información sobre cómo usar Nginx en Linux como servidor proxy inverso para Kestrel, vea Host on Linux
with Nginx (Hospedar en Linux con Nginx).
Apache con Kestrel
Para información sobre cómo usar Apache en Linux como servidor proxy inverso para Kestrel, vea Host on
Linux with Apache (Hospedar en Linux con Apache).
HTTP.sys
ASP.NET Core 2.x
ASP.NET Core 1.x
Si las aplicaciones ASP.NET Core se ejecutan en Windows, HTTP.sys es una alternativa a Kestrel. Suele
recomendarse Kestrel para un rendimiento óptimo. HTTP.sys se puede usar en escenarios en los que la
aplicación se expone a Internet y las características necesarias son compatibles con HTTP.sys pero no Kestrel.
Para información sobre las características de HTTP.sys, vea HTTP.sys.
HTTP.sys también se puede usar para las aplicaciones que solo se exponen a una red interna.
Servidores personalizados
Si los servidores integrados no cumplen los requisitos de la aplicación, se puede crear una implementación de
servidor personalizado. En la guía de Open Web Interface for .NET (OWIN ) se muestra cómo escribir una
implementación de IServer basada en Nowin. Solo requieren la implementación las interfaces de características
que usa la aplicación, aunque como mínimo se debe admitir IHttpRequestFeature e IHttpResponseFeature.
Recursos adicionales
Kestrel
Kestrel con IIS
Hospedaje en Linux con Nginx
Hospedaje en Linux con Apache
HTTP.sys (para ASP.NET Core 1.x, vea WebListener)
Implementación del servidor web Kestrel en
ASP.NET Core
31/05/2018 • 39 minutes to read • Edit Online
WARNING
Si no usa un proxy inverso con el filtrado de hosts habilitado, deberá habilitarlo.
Opciones de Kestrel
ASP.NET Core 2.x
ASP.NET Core 1.x
El servidor web Kestrel tiene opciones de configuración de restricción que son especialmente útiles en las
implementaciones con conexión a Internet. Estos son algunos de los límites importantes que se pueden
personalizar:
Las conexiones máximas de cliente
El tamaño máximo del cuerpo de solicitud
La velocidad mínima de los datos del cuerpo de solicitud.
Establezca estas y otras restricciones en la propiedad Limits de la clase KestrelServerOptions. La propiedad
Limits contiene una instancia de la clase KestrelServerLimits.
Hay un límite independiente para las conexiones que se han actualizado desde HTTP o HTTPS a otro
protocolo (por ejemplo, en una solicitud de WebSockets). Cuando se actualiza una conexión, no se cuenta
con respecto al límite de MaxConcurrentConnections .
.UseKestrel(options =>
{
options.Limits.MaxConcurrentConnections = 100;
options.Limits.MaxConcurrentUpgradedConnections = 100;
options.Limits.MaxRequestBodySize = 10 * 1024;
options.Limits.MinRequestBodyDataRate =
new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
options.Limits.MinResponseDataRate =
new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
options.Listen(IPAddress.Loopback, 5000);
options.Listen(IPAddress.Loopback, 5001, listenOptions =>
{
listenOptions.UseHttps("testCert.pfx", "testPassword");
});
})
[RequestSizeLimit(100000000)]
public IActionResult MyActionMethod()
Este es un ejemplo que muestra cómo configurar la restricción en la aplicación y todas las solicitudes:
.UseKestrel(options =>
{
options.Limits.MaxConcurrentConnections = 100;
options.Limits.MaxConcurrentUpgradedConnections = 100;
options.Limits.MaxRequestBodySize = 10 * 1024;
options.Limits.MinRequestBodyDataRate =
new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
options.Limits.MinResponseDataRate =
new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
options.Listen(IPAddress.Loopback, 5000);
options.Listen(IPAddress.Loopback, 5001, listenOptions =>
{
listenOptions.UseHttps("testCert.pfx", "testPassword");
});
})
Se inicia una excepción si la aplicación intenta configurar el límite de una solicitud después de que la
aplicación haya empezado a leer la solicitud. Hay una propiedad IsReadOnly que señala si la propiedad
MaxRequestBodySize tiene el estado de solo lectura, lo que significa que es demasiado tarde para configurar
el límite.
Velocidad mínima de los datos del cuerpo de la solicitud
MinRequestBodyDataRate
MinResponseDataRate
Kestrel comprueba cada segundo si los datos entran a la velocidad especificada en bytes por segundo. Si la
velocidad está por debajo del mínimo, se agota el tiempo de espera de la conexión. El período de gracia es
la cantidad de tiempo que Kestrel da al cliente para aumentar su velocidad de envío hasta el mínimo; no se
comprueba la velocidad durante ese tiempo. Este período de gracia permite evitar que se interrumpan las
conexiones que inicialmente están enviando datos a una velocidad lenta debido a un inicio lento de TCP.
La velocidad mínima predeterminada es 240 bytes por segundo, con un período de gracia de cinco
segundos.
También se aplica una velocidad mínima a la respuesta. El código para establecer el límite de solicitudes y el
límite de respuestas es el mismo, salvo que tienen RequestBody o Response en los nombres de propiedad y
de interfaz.
Este es un ejemplo que muestra cómo configurar las velocidades de datos mínimas en Program.cs:
.UseKestrel(options =>
{
options.Limits.MaxConcurrentConnections = 100;
options.Limits.MaxConcurrentUpgradedConnections = 100;
options.Limits.MaxRequestBodySize = 10 * 1024;
options.Limits.MinRequestBodyDataRate =
new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
options.Limits.MinResponseDataRate =
new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
options.Listen(IPAddress.Loopback, 5000);
options.Listen(IPAddress.Loopback, 5001, listenOptions =>
{
listenOptions.UseHttps("testCert.pfx", "testPassword");
});
})
ConfigureHttpsDefaults(Action<HttpsConnectionAdapterOptions>)
Especifica una Action de configuración para que se ejecute con cada punto de conexión HTTPS. Al llamar
a ConfigureHttpsDefaults varias veces, se reemplazan las Action anteriores por la última Action
especificada.
Configure(IConfiguration)
Crea un cargador de configuración para configurar Kestrel que toma una IConfiguration como entrada. El
ámbito de la configuración debe corresponderse con la sección de configuración de Kestrel.
ListenOptions.UseHttps
Configure Kestrel para que use HTTPS.
Extensiones de ListenOptions.UseHttps :
UseHttps: configure Kestrel para que use HTTPS con el certificado predeterminado. Produce una
excepción si no hay ningún certificado predeterminado configurado.
UseHttps(string fileName)
UseHttps(string fileName, string password)
UseHttps(string fileName, string password, Action<HttpsConnectionAdapterOptions> configureOptions)
UseHttps(StoreName storeName, string subject)
UseHttps(StoreName storeName, string subject, bool allowInvalid)
UseHttps(StoreName storeName, string subject, bool allowInvalid, StoreLocation location)
UseHttps(StoreName storeName, string subject, bool allowInvalid, StoreLocation location,
Action<HttpsConnectionAdapterOptions> configureOptions)
UseHttps(X509Certificate2 serverCertificate)
UseHttps(X509Certificate2 serverCertificate, Action<HttpsConnectionAdapterOptions> configureOptions)
UseHttps(Action<HttpsConnectionAdapterOptions> configureOptions)
Parámetros de ListenOptions.UseHttps :
filename es la ruta de acceso y el nombre de archivo de un archivo de certificado correspondiente al
directorio donde están los archivos de contenido de la aplicación.
password es la contraseña necesaria para obtener acceso a los datos del certificado X.509.
configureOptions es una Action para configurar HttpsConnectionAdapterOptions . Devuelve
ListenOptions .
storeName es el almacén de certificados desde el que se carga el certificado.
subject es el nombre del sujeto del certificado.
allowInvalid indica si se deben tener en cuenta los certificados no válidos, como los certificados
autofirmados.
location es la ubicación del almacén desde el que se carga el certificado.
serverCertificate es el certificado X.509.
En un entorno de producción, HTTPS se debe configurar explícitamente. Como mínimo, debe existir un
certificado predeterminado.
Estas son las configuraciones compatibles:
Sin configuración
Reemplazar el certificado predeterminado de configuración
Cambiar los valores predeterminados en el código
Sin configuración
Kestrel escucha en http://localhost:5000 y en https://localhost:5001 (si hay disponible un certificado
predeterminado).
Especifique direcciones URL mediante los siguientes elementos:
La variable de entorno ASPNETCORE_URLS .
El argumento de la línea de comandos --urls .
La clave de configuración de host urls .
El método de extensión UseUrls .
Para obtener más información, vea Direcciones URL del servidor e Invalidar la configuración.
El valor que estos métodos suministran puede ser uno o más puntos de conexión HTTP y HTTPS (este
último, si hay disponible un certificado predeterminado). Configure el valor como una lista separada por
punto y coma (por ejemplo, "Urls": "http://localhost:8000;http://localhost:8001" ).
Reemplazar el certificado predeterminado de configuración
WebHost.CreateDefaultBuilder llama a
serverOptions.Configure(context.Configuration.GetSection("Kestrel")) de forma predeterminada para
cargar la configuración de Kestrel. Hay disponible un esquema de configuración de aplicación HTTPS
predeterminado para Kestrel. Configure varios puntos de conexión (incluidas las direcciones URL y los
certificados que va a usar) desde un archivo en disco o desde un almacén de certificados.
En el siguiente ejemplo de appsettings.json:
Establezca AllowInvalid en true para permitir el uso de certificados no válidos (por ejemplo,
certificados autofirmados).
Cualquier punto de conexión HTTPS que no especifique un certificado (HttpsDefaultCert en el
siguiente ejemplo) revierte al certificado definido en Certificados > Predeterminado o al certificado
de desarrollo.
{
"Kestrel": {
"EndPoints": {
"Http": {
"Url": "http://localhost:5000"
},
"HttpsInlineCertFile": {
"Url": "https://localhost:5001",
"Certificate": {
"Path": "<path to .pfx file>",
"Password": "<certificate password>"
}
},
"HttpsInlineCertStore": {
"Url": "https://localhost:5002",
"Certificate": {
"Subject": "<subject; required>",
"Store": "<certificate store; defaults to My>",
"Location": "<location; defaults to CurrentUser>",
"AllowInvalid": "<true or false; defaults to false>"
}
},
"HttpsDefaultCert": {
"Url": "https://localhost:5003"
},
"Https": {
"Url": "https://*:5004",
"Certificate": {
"Path": "<path to .pfx file>",
"Password": "<certificate password>"
}
}
},
"Certificates": {
"Default": {
"Path": "<path to .pfx file>",
"Password": "<certificate password>"
}
}
}
}
Una alternativa al uso de Path y Password en cualquier nodo de certificado consiste en especificar el
certificado por medio de campos del almacén de certificados. Por ejemplo, el certificado en Certificados >
Predeterminado se puede especificar así:
"Default": {
"Subject": "<subject; required>",
"Store": "<cert store; defaults to My>",
"Location": "<location; defaults to CurrentUser>",
"AllowInvalid": "<true or false; defaults to false>"
}
serverOptions.Configure(context.Configuration.GetSection("Kestrel"))
.Endpoint("HTTPS", opt =>
{
opt.HttpsOptions.SslProtocols = SslProtocols.Tls12;
});
options.ConfigureHttpsDefaults(httpsOptions =>
{
httpsOptions.SslProtocols = SslProtocols.Tls12;
});
WebHost.CreateDefaultBuilder()
.UseKestrel((context, options) =>
{
options.ListenAnyIP(5005, listenOptions =>
{
listenOptions.UseHttps(httpsOptions =>
{
var localhostCert = CertificateLoader.LoadFromStoreCert(
"localhost", "My", StoreLocation.CurrentUser,
allowInvalid: true);
var exampleCert = CertificateLoader.LoadFromStoreCert(
"example.com", "My", StoreLocation.CurrentUser,
allowInvalid: true);
var subExampleCert = CertificateLoader.LoadFromStoreCert(
"sub.example.com", "My", StoreLocation.CurrentUser,
allowInvalid: true);
var certs = new Dictionary(StringComparer.OrdinalIgnoreCase);
certs["localhost"] = localhostCert;
certs["example.com"] = exampleCert;
certs["sub.example.com"] = subExampleCert;
return exampleCert;
};
});
});
});
En el ejemplo se configura SSL para un punto de conexión con ListenOptions. Use la misma API para
configurar otras opciones de Kestrel para puntos de conexión específicos.
En Windows, pueden crearse certificados autofirmados con el cmdlet New -SelfSignedCertificate de
PowerShell. Para obtener un ejemplo no compatible, vea UpdateIISExpressSSLForChrome.ps1.
En macOS, Linux y Windows, pueden crearse certificados con OpenSSL.
Enlazar a un socket de Unix
Escuche en un socket de Unix con ListenUnixSocket para mejorar el rendimiento con Nginx, tal y como se
muestra en este ejemplo:
.UseKestrel(options =>
{
options.ListenUnixSocket("/tmp/kestrel-test.sock");
options.ListenUnixSocket("/tmp/kestrel-test.sock", listenOptions =>
{
listenOptions.UseHttps("testCert.pfx", "testpassword");
});
})
Puerto 0
Cuando se especifica el número de puerto 0 , Kestrel se enlaza de forma dinámica a un puerto disponible.
En el siguiente ejemplo se muestra cómo averiguar qué puerto Kestrel está realmente enlazado a un
runtime:
Cuando la aplicación se ejecuta, la salida de la ventana de consola indica el puerto dinámico en el que se
puede tener acceso a la aplicación:
Estos métodos son útiles para que el código funcione con servidores que no sean de Kestrel. Pero tenga en
cuenta estas limitaciones:
SSL no se puede usar con estos métodos, a menos que se proporcione un certificado predeterminado
en la configuración del punto de conexión HTTPS (por ejemplo, por medio de la configuración
KestrelServerOptions o de un archivo de configuración, tal y como se explicó anteriormente en este
tema).
Cuando los métodos Listen y UseUrls se usan al mismo tiempo, los puntos de conexión de Listen
sustituyen a los de UseUrls .
Configuración de puntos de conexión IIS
Cuando se usa IIS, los enlaces de direcciones URL de IIS reemplazan a los enlaces que se hayan
establecido por medio de Listen o de UseUrls . Para más información, vea el tema Módulo ASP.NET Core.
Configuración de transporte
Desde el lanzamiento de ASP.NET Core 2.1, el transporte predeterminado de Kestrel deja de basarse en
Libuv y pasa a basarse en sockets administrados. Se trata de un cambio muy importante para las
aplicaciones ASP.NET Core 2.0 que actualizan a 2.1 y llaman a WebHostBuilderLibuvExtensions.UseLibuv,
y que dependen de cualquiera de los siguientes paquetes:
Microsoft.AspNetCore.Server.Kestrel (referencia de paquete directa)
Microsoft.AspNetCore.App
En el caso de los proyectos de ASP.NET Core 2.1 o posterior que usan el metapaquete
Microsoft.AspNetCore.App y requieren el uso de Libuv:
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv"
Version="2.1.0" />
Llame a WebHostBuilderLibuvExtensions.UseLibuv:
http://65.55.39.10:80/
http://[0:0:0:0:0:ffff:4137:270a]:80/
http://contoso.com:80/
http://*:80/
Los nombres de host, * y + no son especiales. Todo lo que no se identifique como una dirección
IP o un localhost válido se enlaza a todas las direcciones IP de IPv6 e IPv4. Para enlazar distintos
nombres de host a distintas aplicaciones ASP.NET Core en el mismo puerto, use HTTP.sys o un
servidor proxy inverso, como IIS, Nginx o Apache.
WARNING
Si no usa un proxy inverso con el filtrado de hosts habilitado, habilítelo.
Nombre localhost del host con el número de puerto o la IP de bucle invertido con el número de
puerto
http://localhost:5000/
http://127.0.0.1:5000/
http://[::1]:5000/
Cuando se especifica localhost , Kestrel intenta enlazar a las interfaces de bucle invertido de IPv4 e
IPv6. Si el puerto solicitado lo está usando otro servicio en cualquier interfaz de bucle invertido,
Kestrel no se puede iniciar. Si ninguna de estas interfaces de bucle invertido está disponible por
cualquier otra razón (normalmente porque no se admite IPv6), Kestrel registra una advertencia.
Filtrado de hosts
Si bien Kestrel admite una configuración basada en prefijos como http://example.com:5000 , pasa por alto
completamente el nombre de host. El host localhost es un caso especial que se usa para enlazar a
direcciones de bucle invertido. Cualquier otro host que no sea una dirección IP explícita se enlaza a todas
las direcciones IP públicas. Ninguno de estos datos se usa para validar encabezados Host de solicitudes.
Hay dos soluciones alternativas:
Host detrás de un proxy inverso con filtrado de encabezados de host. Este ha sido el único escenario
admitido para Kestrel en ASP.NET Core 1.x.
Usar un middleware para filtrar las solicitudes por el encabezado Host . Este es un middleware de
ejemplo:
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
// A normal middleware would provide an options type, config binding, extension methods, etc..
// This intentionally does all of the work inside of the middleware so it can be
// easily copy-pasted into docs and other projects.
public class HostFilteringMiddleware
{
private readonly RequestDelegate _next;
private readonly IList<string> _hosts;
private readonly ILogger<HostFilteringMiddleware> _logger;
return _next(context);
}
// This does not duplicate format validations that are expected to be performed by the host.
private bool ValidateHost(HttpContext context)
{
StringSegment host = context.Request.Headers[HeaderNames.Host].ToString().Trim();
if (StringSegment.IsNullOrEmpty(host))
{
// Http/1.0 does not require the Host header.
// Http/1.1 requires the header but the value may be empty.
return true;
}
if (colonIndex > 0)
{
host = host.Subsegment(0, colonIndex);
}
return false;
}
}
app.UseMiddleware<HostFilteringMiddleware>();
app.UseMvcWithDefaultRoute();
}
{
"AllowedHosts": "example.com"
}
{
"AllowedHosts": "localhost"
}
Recursos adicionales
Aplicación de HTTPS
Código fuente de Kestrel
Módulo ASP.NET Core
25/06/2018 • 4 minutes to read • Edit Online
Las solicitudes llegan procedentes de Internet al controlador HTTP.sys en modo kernel. El controlador
enruta las solicitudes a IIS en el puerto configurado del sitio web, que suele ser el puerto 80 (HTTP ) o
443 (HTTPS ). El módulo reenvía las solicitudes a Kestrel en un puerto aleatorio de la aplicación, que no
es ni 80 ni 443.
El módulo especifica el puerto a través de la variable de entorno en el inicio y el middleware de
integración de IIS configura el servidor para que escuche en http://localhost:{port} . Se realizan más
comprobaciones y se rechazan las solicitudes que no se hayan originado en el módulo. El módulo no
admite el reenvío de HTTPS, por lo que las solicitudes se reenvían a través de HTTP, aunque IIS las
reciba a través de HTTPS.
Una vez que Kestrel toma una solicitud del módulo, la envía a la canalización de middleware de ASP.NET
Core. La canalización de middleware controla la solicitud y la pasa como una instancia de HttpContext a
la lógica de la aplicación. La respuesta de la aplicación se vuelve a pasar a IIS, que la envía de nuevo al
cliente HTTP que inició la solicitud.
El módulo ASP.NET Core tiene otras funciones. Puede:
Establecer variables de entorno para un proceso de trabajo.
Registrar la salida en un almacenamiento de archivos para solucionar problemas de inicio.
Reenviar tokens de autenticación de Windows.
Recursos adicionales
Hospedaje en Windows con IIS
Referencia de configuración del módulo ASP.NET Core
Repositorio GitHub del módulo ASP.NET Core (código fuente)
Implementación del servidor web HTTP.sys en
ASP.NET Core
25/05/2018 • 14 minutes to read • Edit Online
NOTE
Este tema solo se aplica a ASP.NET Core 2.0 o posterior. En versiones anteriores de ASP.NET Core, HTTP.sys se
denominaba WebListener.
HTTP.sys es un servidor web de ASP.NET Core que solo se ejecuta en Windows. HTTP.sys supone una
alternativa a Kestrel y ofrece algunas características que este no facilita.
IMPORTANT
HTTP.sys no es compatible con el módulo ASP.NET Core y no se puede usar con IIS o IIS Express.
Una implementación interna requiere una característica que no está disponible en Kestrel, como
Autenticación de Windows.
HTTP.sys es una tecnología consolidada que protege contra muchos tipos de ataques y que proporciona la
solidez, la seguridad y la escalabilidad de un servidor web con todas las características. El propio IIS se
ejecuta como agente de escucha de HTTP sobre HTTP.sys.
MaxRequestBodySize
Tamaño máximo permitido de cualquier cuerpo de solicitud en bytes. Cuando se establece en null ,
el tamaño máximo del cuerpo de solicitud es ilimitado. Este límite no tiene ningún efecto en las
conexiones actualizadas, que siempre son ilimitadas.
El método recomendado para reemplazar el límite de una aplicación de ASP.NET Core MVC para un
solo objeto IActionResult consiste en usar el atributo RequestSizeLimitAttribute en un método de
acción:
[RequestSizeLimit(100000000)]
public IActionResult MyActionMethod()
Se inicia una excepción si la aplicación intenta configurar el límite de una solicitud después de que la
aplicación haya empezado a leer la solicitud. Puede usarse una propiedad IsReadOnly que indique si
la propiedad MaxRequestBodySize tiene el estado de solo lectura, lo que significa que es demasiado
tarde para configurar el límite.
Si la aplicación debe reemplazar MaxRequestBodySize por solicitud, use la interfaz
IHttpMaxRequestBodySizeFeature:
logger.LogInformation($"Addresses: {addresses}");
await next.Invoke();
});
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
3. If usa Visual Studio, asegúrese de que la aplicación no está configurada para ejecutar IIS o IIS
Express.
En Visual Studio, el perfil de inicio predeterminado corresponde a IIS Express. Para ejecutar el
proyecto como aplicación de consola, cambie manualmente el perfil seleccionado, tal y como se
muestra en la siguiente captura de pantalla:
Una ventaja de UrlPrefixes es que se genera inmediatamente un mensaje de error para prefijos con
formato incorrecto.
La configuración de UrlPrefixes invalida la configuración de UseUrls / urls / ASPNETCORE_URLS . Por
lo tanto, la ventaja de UseUrls , urls y la variable de entorno ASPNETCORE_URLS es que resulta más
fácil de cambiar entre Kestrel y HTTP.sys. Para obtener más información sobre UseUrls , urls y
ASPNETCORE_URLS , vea el tema Hospedaje en ASP.NET Core.
WARNING
Los enlaces de carácter comodín de nivel superior ( http://*:80/ y http://+:80 ) no se deben usar. Los
enlaces de carácter comodín de nivel superior pueden exponer su aplicación a vulnerabilidades de seguridad.
Esto se aplica tanto a los caracteres comodín fuertes como a los débiles. Use nombres de host explícitos en
lugar de caracteres comodín. Los enlaces de carácter comodín de subdominio (por ejemplo, *.mysub.com ) no
suponen este riesgo de seguridad si se controla todo el dominio primario (a diferencia de *.com , que sí es
vulnerable). Vea la sección 5.4 de RFC 7230 para obtener más información.
3. Registre previamente los prefijos de dirección URL para enlazarse a HTTP.sys y configurar los
certificados x.509.
Si los prefijos de dirección URL no se han registrado previamente en Windows, ejecute la aplicación
con privilegios de administrador. La única excepción se produce al enlazarse a localhost con HTTP (no
HTTPS ) con un número de puerto superior a 1024. En ese caso, no se requieren privilegios de
administrador.
a. La herramienta integrada para configurar HTTP.sys es netsh.exe. netsh.exe se usa para reservar
prefijos de dirección URL y asignar certificados X.509. Esta herramienta requiere privilegios de
administrador.
En este ejemplo se muestran los comandos para reservar prefijos de dirección URL para los
puertos 80 y 443:
Recursos adicionales
API HTTP Server
Repositorio aspnet/HttpSysServer de GitHub (código fuente)
Hospedaje en ASP.NET Core
Globalización y localización en ASP.NET Core
25/06/2018 • 31 minutes to read • Edit Online
By Rick Anderson, Damien Bowden, Bart Calixto, Nadeem Afana y Hisham Bin Ateya
El hecho de crear un sitio web multilingüe con ASP.NET Core permite que este llegue a un público más
amplio. ASP.NET Core proporciona servicios y software intermedio para la localización en diferentes idiomas
y referencias culturales.
La internacionalización conlleva globalización y localización. La globalización es el proceso de diseñar
aplicaciones que admiten diferentes referencias culturales. La globalización agrega compatibilidad con la
entrada, la visualización y la salida de un conjunto definido de scripts de lenguaje relacionados con áreas
geográficas específicas.
La localización es el proceso de adaptar una aplicación globalizada, que ya se ha procesado para la
localizabilidad, a una determinada referencia cultural o configuración regional. Para más información, vea
Términos relacionados con la globalización y la localización al final de este documento.
La localización de la aplicación implica lo siguiente:
1. Hacer que el contenido de la aplicación sea localizable
2. Proporcionar recursos localizados para los idiomas y las referencias culturales admitidos
3. Implementar una estrategia para seleccionar el idioma o la referencia cultural de cada solicitud
namespace Localization.StarterWeb.Controllers
{
[Route("api/[controller]")]
public class AboutController : Controller
{
private readonly IStringLocalizer<AboutController> _localizer;
[HttpGet]
public string Get()
{
return _localizer["About Title"];
}
}
}
namespace Localization.StarterWeb.Controllers
{
public class BookController : Controller
{
private readonly IHtmlLocalizer<BookController> _localizer;
return View();
}
{
public class TestController : Controller
{
private readonly IStringLocalizer _localizer;
private readonly IStringLocalizer _localizer2;
namespace Localization.StarterWeb
{
public class SharedResource
{
}
}
Algunos programadores usan la clase Startup para contener cadenas globales o compartidas. En el ejemplo
siguiente, se usan los localizadores InfoController y SharedResource :
Localización de vista
El servicio IViewLocalizer proporciona cadenas localizadas para una vista. La clase ViewLocalizer
implementa esta interfaz y busca la ubicación del recurso en la ruta de acceso del archivo de vista. En el código
siguiente se muestra cómo usar la implementación predeterminada de IViewLocalizer :
@using Microsoft.AspNetCore.Mvc.Localization
@{
ViewData["Title"] = Localizer["About"];
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>
KEY VALOR
@using Microsoft.AspNetCore.Mvc.Localization
@using Localization.StarterWeb.Services
@{
ViewData["Title"] = Localizer["About"];
}
<h2>@ViewData["Title"].</h2>
<h1>@SharedLocalizer["Hello!"]</h1>
Localización de DataAnnotations
Los mensajes de error de DataAnnotations se localizan con IStringLocalizer<T> . Mediante la opción
ResourcesPath = "Resources" , es posible almacenar los mensajes de error en RegisterViewModel en cualquiera
de las rutas de acceso siguientes:
Resources/ViewModels.Account.RegisterViewModel.fr.resx
Resources/ViewModels/Account/RegisterViewModel.fr.resx
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
En ASP.NET Core MVC 1.1.0 y versiones posteriores, los atributos que no son de validación están localizados.
ASP.NET Core MVC 1.0 no busca cadenas localizadas para los atributos que no son de validación.
Uso de una cadena de recursos para varias clases
En el código siguiente se muestra cómo usar una cadena de recursos para atributos de validación con varias
clases:
Archivos de recursos
Un archivo de recursos es un mecanismo útil para separar del código las cadenas localizables. Las cadenas
traducidas para el idioma no predeterminado son archivos de recursos .resx aislados. Pongamos por caso que
quiere crear un archivo de recursos de español denominado Welcome.es.resx que contenga cadenas
traducidas. "es" es el código de idioma para español. Para crear este archivo de recursos en Visual Studio:
1. En el Explorador de soluciones, haga clic con el botón derecho en la carpeta que contendrá el archivo
de recursos > Agregar > Nuevo elemento.
2. En el cuadro para buscar plantillas instaladas, escriba "recurso" y asigne un nombre al archivo.
3. Escriba el valor de clave (cadena nativa) en la columna Nombre y la cadena traducida en la columna
Valor.
El archivo Welcome.es.resx aparece en Visual Studio.
Resources/Controllers.HomeController.fr.resx Punto
Los archivos de recursos que usan @inject IViewLocalizer en las vistas de Razor siguen un patrón similar.
Para asignar un nombre al archivo de recursos de una vista, se puede usar la nomenclatura de punto o de ruta
de acceso. Los archivos de recursos de la vista de Razor imitan la ruta de acceso de su archivo de vista
asociado. Supongamos que establecemos ResourcesPath en "Resources". En ese caso, el archivo de recursos
de francés asociado a la vista Views/Home/About.cshtml podría ser uno de los siguientes:
Resources/Views/Home/About.fr.resx
Resources/Views.Home.About.fr.resx
Si no usa la opción ResourcesPath , el archivo .resx de una vista se ubicará en la misma carpeta que la vista.
Consideremos, por ejemplo, que quita el designador de referencia cultural ".fr" y la referencia cultural está
establecida en francés. En ese caso, se lee el archivo de recursos predeterminado y se localizan las cadenas. El
Administrador de recursos designa un recurso predeterminado o de reserva para los casos en que nada
coincida con la referencia cultural solicitada. Si quiere simplemente devolver la clave cuando falte un recurso
para la referencia cultural solicitada, no debe tener un archivo de recursos predeterminado.
Generar archivos de recursos con Visual Studio
Si crea un archivo de recursos en Visual Studio sin una referencia cultural en el nombre de archivo (por
ejemplo, Welcome.resx), Visual Studio creará una clase de C# con una propiedad para cada cadena.
Normalmente esto no interesa con ASP.NET Core; por lo general, no tendrá un archivo de recursos .resx
predeterminado (un archivo .resx sin el nombre de la referencia cultural). Se recomienda que cree el archivo
.resx con un nombre de referencia cultural (por ejemplo, Welcome.fr.resx). Cuando cree un archivo .resx con un
nombre de referencia cultural, Visual Studio no generará el archivo de clase. Suponemos que muchos
desarrolladores no crearán un archivo de recursos de idioma predeterminado.
Agregar otras referencias culturales
Cada combinación de idioma y referencia cultural (que no sea el idioma predeterminado) requiere un archivo
de recursos único. Para crear archivos de recursos para otras referencias culturales y configuraciones
regionales, debe crear archivos de recursos en los que los códigos de idioma ISO formen parte del nombre de
archivo (por ejemplo, en-us, fr-ca y en-gb). Estos códigos ISO se colocan entre el nombre de archivo y la
extensión de archivo .resx, como en Welcome.es-MX.resx (español [México]).
services.AddMvc()
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
.AddDataAnnotationsLocalization();
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture(enUSCulture),
// Formatting numbers, dates, etc.
SupportedCultures = supportedCultures,
// UI strings that we have localized.
SupportedUICultures = supportedCultures
});
app.UseStaticFiles();
// To configure external authentication,
// see: http://go.microsoft.com/fwlink/?LinkID=532715
app.UseAuthentication();
app.UseMvcWithDefaultRoute();
La lista predeterminada va de más específico a menos específico. Más adelante en el artículo veremos cómo
puede cambiar el orden e incluso agregar un proveedor de referencia cultural personalizado. Si ninguno de los
proveedores puede determinar la referencia cultural de la solicitud, se usa DefaultRequestCulture .
QueryStringRequestCultureProvider
Algunas aplicaciones usarán una cadena de consulta para establecer la referencia cultural y la referencia
cultural de la interfaz de usuario. Para las aplicaciones que usan el método de cookie o de encabezado Accept-
Language, es útil agregar una cadena de consulta a la dirección URL para depurar y probar el código. De
forma predeterminada, QueryStringRequestCultureProvider está registrado como primer proveedor de
localización en la lista RequestCultureProvider . Debe pasar los parámetros de cadena de consulta culture y
ui-culture . En el ejemplo siguiente, la referencia cultural específica (idioma y región) se establece en español
(México):
http://localhost:5000/?culture=es-MX&ui-culture=es-MX
Si solo pasa uno de los dos parámetros ( culture o ui-culture ), el proveedor de la cadena de consulta usará
el valor que usted ha pasado para establecer ambos valores. Por ejemplo, si solo establece la referencia
cultural, se establecerán Culture y UICulture :
http://localhost:5000/?culture=es-MX
CookieRequestCultureProvider
Las aplicaciones de producción suelen proporcionar un mecanismo para establecer la referencia cultural con la
cookie de la referencia cultural de ASP.NET Core. Use el método MakeCookieValue para crear una cookie.
CookieRequestCultureProvider DefaultCookieName devuelve el nombre de cookie predeterminado usado para
realizar un seguimiento de la información de la referencia cultural preferida del usuario. El nombre de cookie
predeterminado es .AspNetCore.Culture .
El formato de la cookie es c=%LANGCODE%|uic=%LANGCODE% , donde c es Culture y uic es UICulture , por
ejemplo:
c=en-UK|uic=en-US
Si solo especifica uno de los dos valores, ya sea la información de la referencia cultural o la referencia cultural
de la interfaz de usuario, la referencia cultural especificada se usará tanto para la información de la referencia
cultural como para la referencia cultural de la interfaz de usuario.
Encabezado HTTP Accept-Language
El encabezado Accept-Language se puede establecer en la mayoría de los exploradores y está diseñado
inicialmente para especificar el idioma del usuario. Este valor indica qué debe enviar el explorador o qué ha
heredado del sistema operativo subyacente. El encabezado HTTP Accept-Language de una solicitud del
explorador no es un método infalible para detectar el idioma preferido del usuario (vea Setting language
preferences in a browser [Establecer las preferencias de idioma en un explorador]). Una aplicación de
producción debe ofrecer al usuario una manera de personalizar su opción de referencia cultural.
Establecer el encabezado HTTP Accept-Language en Internet Explorer
1. En el icono de engranaje, pulse Opciones de Internet.
2. Haga clic en Lenguajes.
services.Configure<RequestLocalizationOptions>(options =>
{
var supportedCultures = new[]
{
new CultureInfo(enUSCulture),
new CultureInfo("fr")
};
@{
var requestCulture = Context.Features.Get<IRequestCultureFeature>();
var cultureItems = LocOptions.Value.SupportedUICultures
.Select(c => new SelectListItem { Value = c.Name, Text = c.DisplayName })
.ToList();
var returnUrl = string.IsNullOrEmpty(Context.Request.Path) ? "~/" : $"~{Context.Request.Path.Value}";
}
[HttpPost]
public IActionResult SetLanguage(string culture, string returnUrl)
{
Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) }
);
return LocalRedirect(returnUrl);
}
No se puede conectar el archivo _SelectLanguagePartial.cshtml con código de ejemplo para este proyecto. El
proyecto Localization.StarterWeb de GitHub tiene código para hacer fluir RequestLocalizationOptions a una
vista parcial de Razor a través del contenedor de inserción de dependencias.
Recursos adicionales
Proyecto Localization.StarterWeb usado en el artículo
Archivos de recursos en Visual Studio
Recursos en archivos .resx
Kit de herramientas de aplicaciones multilingüe de Microsoft
Configurar la localización de objetos portátiles en
ASP.NET Core
25/06/2018 • 12 minutes to read • Edit Online
#: Services/EmailService.cs:29
msgid "Enter a comma separated list of email addresses."
msgstr "Entrez une liste d'emails séparés par une virgule."
#: Views/Email.cshtml:112
msgid "The email address is \"{0}\"."
msgid_plural "The email addresses are \"{0}\"."
msgstr[0] "L'adresse email est \"{0}\"."
msgstr[1] "Les adresses email sont \"{0}\""
Registrar el servicio
Agregue los servicios necesarios al método ConfigureServices de Startup.cs:
services.AddPortableObjectLocalization();
services.Configure<RequestLocalizationOptions>(options =>
{
var supportedCultures = new List<CultureInfo>
{
new CultureInfo("en-US"),
new CultureInfo("en"),
new CultureInfo("fr-FR"),
new CultureInfo("fr")
};
app.UseStaticFiles();
app.UseRequestLocalization();
app.UseMvcWithDefaultRoute();
}
Agregue el código siguiente a la vista de Razor de su elección. En este ejemplo se usa About.cshtml.
@using Microsoft.AspNetCore.Mvc.Localization
@inject IViewLocalizer Localizer
<p>@Localizer["Hello world!"]</p>
Se inserta una instancia de IViewLocalizer que se usa para traducir el texto "Hello world!".
Crear un archivo de objeto portátil
Cree un archivo denominado .po en la carpeta raíz de la aplicación. En este ejemplo, el nombre del archivo es fr.po
porque se usa el idioma francés:
En este archivo se almacenan la cadena que se va a traducir y la cadena traducida al francés. Si es necesario, las
traducciones se revierten a su referencia cultural principal. En este ejemplo, el archivo fr.po se usa si la referencia
cultural solicitada es fr-FR o fr-CA .
Probar la aplicación
Ejecute la aplicación y vaya a la URL /Home/About . El texto Hello world! aparece en pantalla.
Vaya a la dirección URL /Home/About?culture=fr-FR . El texto Bonjour le monde! aparece en pantalla.
Pluralización
Los archivos de objeto portátil son compatibles con las formas de pluralización, lo que resulta útil cuando es
necesario traducir la misma cadena de forma diferente en función de una cardinalidad. Esta tarea se ve dificultada
por el hecho de que cada idioma define reglas personalizadas para seleccionar qué cadena se va a usar en función
de la cardinalidad.
El paquete de localización de Orchard proporciona una API para invocar automáticamente estas diferentes formas
plurales.
Crear archivos de objeto portátil de pluralización
Agregue el contenido siguiente al archivo fr.po mencionado anteriormente:
msgid "There is one item."
msgid_plural "There are {0} items."
msgstr[0] "Il y a un élément."
msgstr[1] "Il y a {0} éléments."
Vea ¿Qué es un archivo de objeto portátil? para saber qué representa cada entrada de este ejemplo.
Agregar un idioma con formas de pluralización diferentes
En el ejemplo anterior se han usado cadenas en inglés y francés. El idioma inglés y francés solo tienen dos formas
de pluralización y comparten las mismas reglas de formato, es decir, se asigna una cardinalidad de uno a la primera
forma plural. Todas las demás cardinalidades se asignan a la segunda forma plural.
No todos los idiomas comparten las mismas reglas. Esto se muestra con el idioma checo, que tiene tres formas
plurales.
Cree el archivo cs.po como se indica a continuación y observe que la pluralización requiere tres traducciones
diferentes:
Para aceptar localizaciones en checo, agregue "cs" a la lista de referencias culturales admitidas en el método
ConfigureServices :
Edite el archivo Views/Home/About.cshtml para representar cadenas localizadas en plural para diversas
cardinalidades:
Nota: En un escenario real, se usaría una variable para representar el número. En este caso, repetimos el mismo
código con tres valores diferentes para exponer un caso muy específico.
Si cambia las referencias culturales, verá lo siguiente:
Para /Home/About :
Il y a un élément.
Il y a 2 éléments.
Il y a 5 éléments.
Para /Home/About?culture=cs :
Tenga en cuenta que para la referencia cultural de checo, las tres traducciones son diferentes. Las referencias
culturales de inglés y francés comparten la misma construcción para las dos últimas cadenas traducidas.
Tareas avanzadas
Contextualización de cadenas
Las aplicaciones a menudo contienen las cadenas que se van a traducir en lugares diferentes. La misma cadena
puede tener una traducción diferente en determinadas ubicaciones dentro de una aplicación (vistas de Razor o
archivos de clase). Un archivo de objeto portátil admite el concepto de contexto de archivo, que se puede usar para
clasificar la cadena que se va a representar. Mediante el uso de un contexto de archivo, una cadena se puede
traducir de forma diferente según el contexto de archivo (o según la falta de contexto de archivo).
Los servicios de localización de objetos portátiles usan el nombre de la clase completa o la vista que se usa al
traducir una cadena. Esto se logra mediante el establecimiento del valor en la entrada msgctxt .
Considere la posibilidad de realizar una adición mínima al ejemplo fr.po anterior. Una vista de Razor ubicada en
Views/Home/About.cshtml se puede definir como el contexto de archivo si se establece el valor de entrada
reservado msgctxt :
msgctxt "Views.Home.About"
msgid "Hello world!"
msgstr "Bonjour le monde!"
En este ejemplo, los archivos de objeto portátil se cargan desde la carpeta Localization.
Implementar una lógica personalizada para buscar archivos de localización
Cuando se necesita una lógica más compleja para buscar archivos de objeto portátil, es posible implementar y
registrar la interfaz OrchardCore.Localization.PortableObject.ILocalizationFileLocationProvider como un servicio.
Esto es útil cuando los archivos de objeto portátil se pueden almacenar en ubicaciones diferentes o cuando los
archivos deben encontrarse en una jerarquía de carpetas.
Usar un idioma pluralizado predeterminado diferente
El paquete incluye un método de extensión Plural que es específico para dos formas plurales. Para los idiomas
que requieren más formas plurales, es necesario crear un método de extensión. Con un método de extensión, no
tendrá que proporcionar ningún archivo de localización para el idioma predeterminado, dado que las cadenas
originales ya están disponibles directamente en el código.
Puede usar la sobrecarga Plural(int count, string[] pluralForms, params object[] arguments) más genérica, que
acepta una matriz de cadenas de traducciones.
Inicio de solicitudes HTTP
19/06/2018 • 23 minutes to read • Edit Online
Patrones de consumo
IHttpClientFactory se puede usar de varias formas en una aplicación:
Uso básico
Clientes con nombre
Clientes con tipo
Clientes generados
Ninguno de ellos es rigurosamente superior a otro, sino que el mejor método dependerá de las restricciones
particulares de la aplicación.
Uso básico
Se puede registrar un IHttpClientFactory llamando al método de extensión AddHttpClient en
IServiceCollection , dentro del método ConfigureServices en Startup.cs.
services.AddHttpClient();
Una vez registrado, el código puede aceptar un IHttpClientFactory en cualquier parte donde se puedan insertar
servicios por medio de la inserción de dependencias. El IHttpClientFactory se puede usar para crear una instancia
de HttpClient :
public class BasicUsageModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
if (response.IsSuccessStatusCode)
{
Branches = await response.Content.ReadAsAsync<IEnumerable<GitHubBranch>>();
}
else
{
GetBranchesError = true;
Branches = Array.Empty<GitHubBranch>();
}
}
}
Cuando IHttpClientFactory se usa de este modo, constituye una excelente manera de refactorizar una aplicación
existente. No tiene efecto alguno en la forma en que HttpClient se usa. En aquellos sitios en los que ya se hayan
creado instancias de HttpClient , reemplace esas apariciones por una llamada a CreateClient .
Clientes con nombre
Si una aplicación necesita usar HttpClient de diversas maneras, cada una con una configuración diferente, una
opción consiste en usar clientes con nombre. La configuración de un HttpClient con nombre se puede realizar
durante la fase de registro en ConfigureServices .
services.AddHttpClient("github", c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); // Github API versioning
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample"); // Github requires a user-agent
});
En el código anterior, se llama a AddHttpClient usando el nombre "github". Este cliente tiene aplicadas algunas
configuraciones predeterminadas, a saber, la dirección base y dos encabezados necesarios para trabajar con la API
de GitHub.
Cada vez que se llama a CreateClient , se crea otra instancia de HttpClient y se llama a la acción de
configuración.
Para consumir un cliente con nombre, se puede pasar un parámetro de cadena a CreateClient . Especifique el
nombre del cliente que se va a crear:
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
if (response.IsSuccessStatusCode)
{
PullRequests = await response.Content.ReadAsAsync<IEnumerable<GitHubPullRequest>>();
}
else
{
GetPullRequestsError = true;
PullRequests = Array.Empty<GitHubPullRequest>();
}
}
}
En el código anterior, no es necesario especificar un nombre de host en la solicitud. Basta con pasar solo la ruta de
acceso, ya que se usa la dirección base configurada del cliente.
Clientes con tipo
Los clientes con tipo proporcionan las mismas funciones que los clientes con nombre sin la necesidad de usar
cadenas como claves. El método del cliente con tipo proporciona ayuda de compilador e IntelliSense al consumir
clientes. Ofrecen una sola ubicación para configurar un determinado HttpClient e interactuar con él. Por ejemplo,
el mismo cliente con tipo se puede usar para un punto de conexión back-end único y encapsular toda la lógica que
se ocupa de ese punto de conexión. Otra ventaja es que funcionan con la inserción de dependencias, de modo que
se pueden insertar cuando sea necesario en la aplicación.
Un cliente con tipo acepta un parámetro HttpClient en su constructor:
public class GitHubService
{
public HttpClient Client { get; }
Client = client;
}
response.EnsureSuccessStatusCode();
return result;
}
}
En el código anterior, la configuración se mueve al cliente con tipo. El objeto HttpClient se expone como una
propiedad pública. Se pueden definir métodos específicos de API que exponen la funcionalidad HttpClient . El
método GetAspNetDocsIssues encapsula el código necesario para consultar y analizar los últimos problemas
abiertos de un repositorio de GitHub.
Para registrar un cliente con tipo, se puede usar el método de extensión genérico AddHttpClient dentro de
ConfigureServices , especificando la clase del cliente con tipo:
services.AddHttpClient<GitHubService>();
El cliente con tipo se registra como transitorio con inserción con dependencias, y se puede insertar y consumir
directamente:
public class TypedClientModel : PageModel
{
private readonly GitHubService _gitHubService;
services.AddHttpClient<RepoService>(c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
El HttpClient se puede encapsular completamente dentro de un cliente con nombre. En lugar de exponerlo como
una propiedad, se pueden proporcionar métodos públicos que llamen a la instancia de HttpClient internamente.
response.EnsureSuccessStatusCode();
return result;
}
}
En el código anterior, el HttpClient se almacena como un campo privado. Todo el acceso para realizar llamadas
externas pasa por el método GetRepos .
Clientes generados
IHttpClientFactory se puede usar en combinación con otras bibliotecas de terceros, como Refit. Refit es una
biblioteca de REST para .NET que convierte las API de REST en interfaces en vivo. Se genera una implementación
de la interfaz dinámicamente por medio de RestService , usando HttpClient para realizar las llamadas HTTP
externas.
Se define una interfaz y una respuesta para representar la API externa y su correspondiente respuesta:
Un cliente con tipo se puede agregar usando Refit para generar la implementación:
services.AddMvc();
}
La interfaz definida se puede usar cuando sea preciso con la implementación proporcionada por la inserción de
dependencias y Refit:
[ApiController]
public class ValuesController : ControllerBase
{
private readonly IHelloClient _client;
[HttpGet("/")]
public async Task<ActionResult<Reply>> Index()
{
return await _client.GetMessageAsync();
}
}
services.AddTransient<ValidateHeaderHandler>();
services.AddHttpClient("externalservice", c =>
{
c.BaseAddress = new Uri("https://localhost:5000/"); // assume this is an "external" service which requires
an API KEY
})
.AddHttpMessageHandler<ValidateHeaderHandler>();
Se pueden registrar varios controladores en el orden en que deben ejecutarse. Cada controlador contiene el
siguiente controlador hasta que el último HttpClientHandler ejecuta la solicitud:
services.AddTransient<SecureRequestHandler>();
services.AddTransient<RequestDataHandler>();
services.AddHttpClient("clientwithhandlers")
// This handler is on the outside and called first during the request, last during the response.
.AddHttpMessageHandler<SecureRequestHandler>()
// This handler is on the inside, closest to the request being sent.
.AddHttpMessageHandler<RequestDataHandler>();
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.0-rc1-final" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="2.1.0-rc1-final" />
<PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="2.1.0-rc1-final" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.1.0-rc1-final" />
</ItemGroup>
</Project>
Tras restaurar este paquete, hay métodos de extensión disponibles para admitir la adición de controladores
basados en Polly en clientes.
Control de errores transitorios
Los errores más comunes que se puede esperar que ocurran al realizar llamadas HTTP externas serán transitorios.
Por ello, se incluye un método de extensión muy práctico denominado AddTransientHttpErrorPolicy , que permite
definir una directiva para controlar los errores transitorios. Las directivas que se configuran con este método de
extensión controlan HttpRequestException , las respuestas HTTP 5xx y las respuestas HTTP 408.
La extensión AddTransientHttpErrorPolicy se puede usar en ConfigureServices . La extensión da acceso a un
objeto PolicyBuilder , configurado para controlar los errores que pueden constituir un posible error transitorio:
services.AddHttpClient<UnreliableEndpointCallerService>()
.AddTransientHttpErrorPolicy(p => p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));
En el código anterior, se define una directiva WaitAndRetryAsync . Las solicitudes erróneas se reintentan hasta tres
veces con un retardo de 600 ms entre intentos.
Seleccionar directivas dinámicamente
Existen más métodos de extensión que pueden servir para agregar controladores basados en Polly. Una de esas
extensiones es AddPolicyHandler , que tiene varias sobrecargas. Una de esas sobrecargas permite inspeccionar la
solicitud al dilucidar qué directiva aplicar:
services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
.AddPolicyHandler(request => request.Method == HttpMethod.Get ? timeout : longTimeout);
En el código anterior, si la solicitud de salida es GET, se aplica un tiempo de espera de 10 segundos. En cualquier
otro método HTTP, se usa un tiempo de espera de 30 segundos.
Agregar varios controladores de Polly
Es habitual anidar directivas de Polly para proporcionar una mejor funcionalidad:
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
registry.Add("regular", timeout);
registry.Add("long", longTimeout);
services.AddHttpClient("regulartimeouthandler")
.AddPolicyHandlerFromRegistry("regular");
En el código anterior, se agrega un elemento PolicyRegistry a ServiceCollection y, con él, se registran dos
directivas. Para poder usar una directiva del Registro, se usa el método AddPolicyHandlerFromRegistry , pasando el
nombre de la directiva que se va a aplicar.
Encontrará más información sobre IHttpClientFactory y las integraciones de Polly en la wiki de Polly.
La agrupación de controladores es conveniente porque cada controlador suele administrar sus propias conexiones
HTTP subyacentes. Crear más controladores de lo necesario puede provocar retrasos en la conexión. Además,
algunos controladores dejan las conexiones abiertas de forma indefinida, lo que puede ser un obstáculo a la hora
de reaccionar ante los cambios de DNS.
La duración de controlador predeterminada es dos minutos. El valor predeterminado se puede reemplazar
individualmente en cada cliente con nombre. Para ello, llame a SetHandlerLifetime en el IHttpClientBuilder que
se devuelve cuando se crea el cliente:
services.AddHttpClient("extendedhandlerlifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
Registro
Los clientes que se han creado a través de IHttpClientFactory registran mensajes de registro de todas las
solicitudes. Deberá habilitar el nivel de información adecuado en la configuración del registro para ver los
mensajes de registro predeterminados. El registro de más información, como el registro de encabezados de
solicitud, solo se incluye en el nivel de seguimiento.
La categoría de registro usada en cada cliente incluye el nombre del cliente. Así, por ejemplo, un cliente llamado
"MyNamedClient" registra mensajes con una categoría System.Net.Http.HttpClient.MyNamedClient.LogicalHandler .
Los mensajes con el sufijo "LogicalHandler" se producen fuera de la canalización de controlador de la solicitud. En
la solicitud, los mensajes se registran antes de que cualquier otro controlador de la canalización haya procesado la
solicitud. En la respuesta, los mensajes se registran después de que cualquier otro controlador de la canalización
haya recibido la respuesta.
El registro también se produce dentro de la canalización de controlador de la solicitud. En nuestro caso de ejemplo
de "MyNamedClient", esos mensajes se registran en la categoría de registro
System.Net.Http.HttpClient.MyNamedClient.ClientHandler . En la solicitud, esto tiene lugar después de que todos los
demás controladores se hayan ejecutado y justo antes de que la solicitud se envíe por la red. En la respuesta, este
registro incluye el estado de la respuesta antes de que vuelva a pasar por la canalización del controlador.
Si se habilita el registro tanto dentro como fuera de la canalización, se podrán inspeccionar los cambios realizados
por otros controladores de la canalización. Esto puede englobar cambios, por ejemplo, en los encabezados de
solicitud o en el código de estado de la respuesta.
Si el nombre del cliente se incluye en la categoría de registro, dicho registro se podrá filtrar para encontrar clientes
con nombre específicos cuando sea necesario.
Configurar HttpMessageHandler
Puede que sea necesario controlar la configuración del elemento HttpMessageHandler interno usado por un cliente.
Se devuelve un IHttpClientBuilder cuando se agregan clientes con nombre o con tipo. Se puede usar el método
de extensión ConfigurePrimaryHttpMessageHandler para definir un delegado. Este delegado servirá para crear y
configurar el elemento principal HttpMessageHandler que ese cliente usa:
services.AddHttpClient("configured-inner-handler")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
Características de solicitud de ASP.NET Core
25/06/2018 • 5 minutes to read • Edit Online
Interfaces de características
ASP.NET Core define una serie de interfaces de características HTTP en Microsoft.AspNetCore.Http.Features que
los servidores usan para identificar las características que admiten. Las siguientes interfaces de características
controlan las solicitudes y devuelven respuestas:
IHttpRequestFeature Define la estructura de una solicitud HTTP; esto es, el protocolo, la ruta de acceso, la cadena
de consulta, los encabezados y el cuerpo.
IHttpResponseFeature Define la estructura de una respuesta HTTP; esto es, el código de estado, los encabezados y
el cuerpo de la respuesta.
IHttpAuthenticationFeature Define la capacidad para identificar usuarios según un ClaimsPrincipal y para
especificar un controlador de autenticación.
IHttpUpgradeFeature Define la compatibilidad con actualizaciones HTTP, que permiten al cliente especificar otros
protocolos que le gustaría usar en caso de que el servidor cambie de protocolo.
IHttpBufferingFeature Define métodos para deshabilitar el almacenamiento en búfer de solicitudes o respuestas.
IHttpConnectionFeature Define las propiedades de las direcciones y los puertos tanto locales como remotos.
IHttpRequestLifetimeFeature Define la capacidad para anular conexiones o para detectar si una solicitud ha
finalizado antes de tiempo (por ejemplo, debido a una desconexión del cliente).
IHttpSendFileFeature Define un método para enviar archivos de forma asincrónica.
IHttpWebSocketFeature Define una API para admitir sockets web.
IHttpRequestIdentifierFeature Agrega una propiedad que se puede implementar para distinguir solicitudes de
forma única.
ISessionFeature Define abstracciones ISessionFactory e ISession para admitir sesiones de usuario.
ITlsConnectionFeature Define una API para recuperar certificados de cliente.
ITlsTokenBindingFeature Define métodos para trabajar con parámetros de enlace de tokens de TLS.
NOTE
ISessionFeature no es una característica de servidor, pero se implementa por medio de SessionMiddleware . Vea
Introduction to session and application state in ASP.NET Core (Introducción al estado de sesión y la aplicación de ASP.NET
Core).
Colecciones de características
La propiedad Features de HttpContext proporciona una interfaz para obtener y establecer las características
HTTP disponibles para la solicitud actual. Puesto que la colección de características es mutable incluso en el
contexto de una solicitud, se puede usar middleware para modificarla y para agregar compatibilidad con más
características.
Resumen
Las interfaces de características definen las características HTTP concretas que una solicitud determinada puede
admitir. Los servidores definen colecciones de características y el conjunto inicial de características admitidas por
esos servidores, pero el middleware puede servir para mejorar estas características.
Recursos adicionales
Servidores
Middleware
Apertura de la interfaz web para .NET (OWIN )
Primitivas en ASP.NET Core
21/06/2018 • 2 minutes to read • Edit Online
Las primitivas de ASP.NET Core son los bloques de creación de bajo nivel compartidos por extensiones de la
plataforma. Puede usar estos bloques de creación en su propio código.
Detectar cambios con tokens de cambio
Detección de cambios con tokens de cambio en
ASP.NET Core
25/05/2018 • 16 minutes to read • Edit Online
Interfaz IChangeToken
IChangeToken propaga notificaciones que indican que se ha producido un cambio. IChangeToken reside en el
espacio de nombres Microsoft.Extensions.Primitives. Para las aplicaciones que no usan el metapaquete
Microsoft.AspNetCore.All, haga referencia al paquete NuGet Microsoft.Extensions.Primitives en el archivo de
proyecto.
IChangeToken tiene dos propiedades:
ActiveChangedCallbacks indica si el token genera devoluciones de llamada de forma proactiva. Si
ActiveChangedCallbacks se establece en false , nunca se llama a una devolución de llamada y la aplicación
debe sondear HasChanged en busca de cambios. También es posible que un token nunca se cancele si no se
producen cambios o si se elimina o deshabilita el agente de escucha de cambios subyacente.
HasChanged obtiene un valor que indica si se ha producido un cambio.
La interfaz tiene un método, RegisterChangeCallback(Acción<Objeto>, Objeto), que registra una devolución de
llamada que se invoca cuando el token ha cambiado. HasChanged se debe establecer antes de que se invoque la
devolución de llamada.
Clase ChangeToken
ChangeToken es una clase estática que se usa para propagar notificaciones que indican que se ha producido un
cambio. ChangeToken reside en el espacio de nombres Microsoft.Extensions.Primitives. Para las aplicaciones que
no usan el metapaquete Microsoft.AspNetCore.All, haga referencia al paquete NuGet
Microsoft.Extensions.Primitives en el archivo de proyecto.
El método OnChange(Función<IChangeToken>, Acción) de ChangeToken registra una Action que se llama cada
vez que cambia el token:
Func<IChangeToken> genera el token.
Se llama a Action cuando cambia el token.
ChangeToken tiene una sobrecarga OnChange<TState>(Función<IChangeToken>, Acción<TState>, TState) que
toma un parámetro TState adicional que se pasa a la Action de consumidor de token.
OnChange devuelve una interfaz IDisposable. Al llamar a Dispose se detiene la escucha del token de futuras
modificaciones y se liberan sus recursos.
while(runCount < 4)
{
try
{
if (File.Exists(filePath))
{
using (var fs = File.OpenRead(filePath))
{
return System.Security.Cryptography.SHA1.Create().ComputeHash(fs);
}
}
else
{
throw new FileNotFoundException();
}
}
catch (IOException ex)
{
if (runCount == 3 || ex.HResult != -2147024864)
{
throw;
}
else
{
Thread.Sleep(TimeSpan.FromSeconds(Math.Pow(2, runCount)));
runCount++;
}
}
}
Se implementa un reintento con una interrupción exponencial. El reintento aparece porque se puede producir un
bloqueo de archivos que impida temporalmente calcular un hash nuevo en uno de los archivos.
Token de cambio de inicio simple
Registre una devolución de llamada de Action de consumidor de token para las notificaciones de cambio en el
token de recarga de configuración (Startup.cs):
ChangeToken.OnChange(
() => config.GetReloadToken(),
(state) => InvokeChanged(state),
env);
if (!_appsettingsHash.SequenceEqual(appsettingsHash) ||
!_appsettingsEnvHash.SequenceEqual(appsettingsEnvHash))
{
_appsettingsHash = appsettingsHash;
_appsettingsEnvHash = appsettingsEnvHash;
El state de la devolución de llamada se usa para pasar el IHostingEnvironment . Esto es útil para determinar el
archivo JSON de configuración appsettings correcto que se va a supervisar, appsettings.<Entorno>.json. Se usa el
hash de archivo para evitar que se ejecute varias veces la instrucción WriteConsole debido a varias devoluciones
de llamada de token cuando el archivo de configuración solo ha cambiado una vez.
Este sistema se ejecuta siempre que la aplicación esté en ejecución y el usuario no lo puede deshabilitar.
Supervisión de los cambios de configuración como servicio
En el ejemplo se implementa lo siguiente:
La supervisión del token de inicio básico.
La supervisión como servicio.
Un mecanismo para habilitar y deshabilitar la supervisión.
En el ejemplo se establece una interfaz IConfigurationMonitor (Extensions/ConfigurationMonitor.cs):
El constructor de la clase implementada, ConfigurationMonitor , registra una devolución de llamada para las
notificaciones de cambio:
ChangeToken.OnChange<IConfigurationMonitor>(
() => config.GetReloadToken(),
InvokeChanged,
this);
}
if (!_appsettingsHash.SequenceEqual(appsettingsHash) ||
!_appsettingsEnvHash.SequenceEqual(appsettingsEnvHash))
{
string message = $"State updated at {DateTime.Now}";
_appsettingsHash = appsettingsHash;
_appsettingsEnvHash = appsettingsEnvHash;
services.AddSingleton<IConfigurationMonitor, ConfigurationMonitor>();
public IndexModel(
IConfiguration config,
IConfigurationMonitor monitor,
FileService fileService)
{
_config = config;
_monitor = monitor;
_fileService = fileService;
}
return RedirectToPage();
}
return RedirectToPage();
}
while(runCount < 4)
{
try
{
if (File.Exists(filePath))
{
using (var fileStreamReader = File.OpenText(filePath))
{
return await fileStreamReader.ReadToEndAsync();
}
}
else
{
throw new FileNotFoundException();
}
}
catch (IOException ex)
{
if (runCount == 3 || ex.HResult != -2147024864)
{
throw;
}
else
{
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, runCount)));
runCount++;
}
}
}
return null;
}
Se crea un FileService para administrar las búsquedas de archivos en caché. La llamada al método
GetFileContent del servicio intenta obtener el contenido de archivo de la caché en memoria y devolverlo al autor
de la llamada (Services/FileService.cs).
Si el contenido en caché no se encuentra mediante la clave de caché, se realizan las acciones siguientes:
1. El contenido del archivo se obtiene mediante GetFileContent .
2. Se obtiene un token de cambio del proveedor de archivos con IFileProviders.Watch. La devolución de llamada
del token se desencadena cuando se modifica el archivo.
3. El contenido del archivo se almacena en caché con un período de vencimiento variable. El token de cambio se
adjunta con MemoryCacheEntryExtensions.AddExpirationToken para expulsar la entrada de caché si el archivo
cambia mientras está almacenado en caché.
public class FileService
{
private readonly IMemoryCache _cache;
private readonly IFileProvider _fileProvider;
private List<string> _tokens = new List<string>();
if (fileContent != null)
{
// Obtain a change token from the file provider whose
// callback is triggered when the file is modified.
var changeToken = _fileProvider.Watch(fileName);
return fileContent;
}
return string.Empty;
}
}
services.AddMemoryCache();
services.AddSingleton<FileService>();
Clase CompositeChangeToken
Para representar una o varias instancias de IChangeToken en un solo objeto, use la clase CompositeChangeToken.
var compositeChangeToken =
new CompositeChangeToken(
new List<IChangeToken>
{
firstCancellationChangeToken,
secondCancellationChangeToken
});
En el token compuesto, HasChanged notifica true si algún token representado HasChanged es true . En el token
compuesto, ActiveChangeCallbacks notifica true si algún token representado ActiveChangeCallbacks es true . Si
se producen varios eventos de cambio simultáneos, la devolución de llamada de cambio compuesto se invoca
exactamente una vez.
Vea también
Almacenamiento en caché en memoria
Trabajar con una memoria caché distribuida
Almacenamiento en caché de respuestas
Middleware de almacenamiento en caché de respuestas
Aplicación auxiliar de etiquetas de caché
Aplicación auxiliar de etiquetas de caché distribuida
Interfaz web abierta para .NET (OWIN) con ASP.NET
Core
25/06/2018 • 9 minutes to read • Edit Online
NOTE
Los encabezados de respuesta solo deben modificarse antes de la primera escritura en la secuencia de respuesta.
NOTE
No se recomienda la realización de varias llamadas a UseOwin por motivos de rendimiento. Los componentes de OWIN
funcionarán mejor si se agrupan.
app.UseOwin(pipeline =>
{
pipeline(async (next) =>
{
// do something before
await OwinHello(new OwinEnvironment(HttpContext));
// do something after
});
});
namespace NowinSample
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseNowin()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}
IServer es una interfaz que requiere una propiedad Features y un método Start .
Start se encarga de configurar e iniciar el servidor, lo que en este caso se lleva a cabo mediante una serie de
llamadas API fluidas que establecen direcciones analizadas desde IServerAddressesFeature. Tenga en cuenta que
la configuración fluida de la variable _builder especifica que las solicitudes se controlarán mediante el valor
appFunc definido anteriormente en el método. Se llama a este valor Func en cada solicitud para procesar las
solicitudes entrantes.
Agregaremos también una extensión IWebHostBuilder para que sea fácil de agregar y configurar el servidor
Nowin.
using System;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.Extensions.DependencyInjection;
using Nowin;
using NowinSample;
namespace Microsoft.AspNetCore.Hosting
{
public static class NowinWebHostBuilderExtensions
{
public static IWebHostBuilder UseNowin(this IWebHostBuilder builder)
{
return builder.ConfigureServices(services =>
{
services.AddSingleton<IServer, NowinServer>();
});
}
Después de hacer estas implementaciones, invoque la extensión en Program.cs para ejecutar una aplicación
ASP.NET a través de este servidor personalizado:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
namespace NowinSample
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseNowin()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}
app.Run(context =>
{
return context.Response.WriteAsync("Hello World");
});
}
while (!webSocket.CloseStatus.HasValue)
{
// Echo anything we receive
await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, received.Count),
received.MessageType, received.EndOfMessage, CancellationToken.None);
await webSocket.CloseAsync(webSocket.CloseStatus.Value,
webSocket.CloseStatusDescription, CancellationToken.None);
}
}
Este ejemplo se ha configurado con el mismo NowinServer que el anterior; la única diferencia es la manera en
que la aplicación está configurada en su método Configure . La aplicación se muestra mediante una prueba que
usa un cliente WebSocket simple:
Entorno de OWIN
Puede construir un entorno de OWIN por medio de HttpContext .
Claves de OWIN
OWIN depende de un objeto IDictionary<string,object> para comunicar información mediante un intercambio
de solicitud/respuesta HTTP. ASP.NET Core implementa las claves que se enumeran a continuación. Vea las
extensiones de la especificación principal y las directrices principales y claves comunes de OWIN.
Datos de solicitud (OWIN v1.0.0)
KEY VALOR (TIPO) DESCRIPTION
owin.RequestScheme String
owin.RequestMethod String
owin.RequestPathBase String
owin.RequestPath String
owin.RequestQueryString String
owin.RequestProtocol String
owin.RequestHeaders IDictionary<string,string[]>
KEY VALOR (TIPO) DESCRIPTION
owin.RequestBody Stream
owin.ResponseHeaders IDictionary<string,string[]>
owin.ResponseBody Stream
owin.CallCancelled CancellationToken
owin.Version String
Claves comunes
KEY VALOR (TIPO) DESCRIPTION
ssl.ClientCertificate X509Certificate
ssl.LoadClientCertAsync Func<Task>
server.RemoteIpAddress String
server.RemotePort String
server.LocalIpAddress String
server.LocalPort String
server.IsLocal bool
server.OnSendingHeaders Action<Action<object>,object>
SendFiles v0.3.0
KEY VALOR (TIPO) DESCRIPTION
Opaque v0.3.0
KEY VALOR (TIPO) DESCRIPTION
opaque.Version String
opaque.Stream Stream
opaque.CallCancelled CancellationToken
WebSocket v0.3.0
KEY VALOR (TIPO) DESCRIPTION
websocket.Version String
websocket.CallCancelled CancellationToken
Recursos adicionales
Middleware
Servidores
Compatibilidad con WebSockets en ASP.NET Core
25/06/2018 • 9 minutes to read • Edit Online
Requisitos previos
ASP.NET Core 1.1 o posterior
Cualquier sistema operativo que admita ASP.NET Core:
Windows 7/Windows Server 2008 o posterior
Linux
macOS
Si la aplicación se ejecuta en Windows con IIS:
Windows 8/Windows Server 2012 o versiones posteriores
IIS 8/Express IIS 8
WebSockets debe estar habilitado en IIS (vea la sección Compatibilidad con IIS/IIS Express).
Si la aplicación se ejecuta en HTTP.sys:
Windows 8/Windows Server 2012 o versiones posteriores
Para saber qué exploradores son compatibles, vea https://caniuse.com/#feat=websockets.
Cómo se usa
Instale el paquete Microsoft.AspNetCore.WebSockets.
Configure el middleware.
Acepte las solicitudes WebSocket.
Envíe y reciba mensajes.
Configurar el middleware
Agregue el middleware de WebSockets al método Configure de la clase Startup :
app.UseWebSockets();
});
Una solicitud WebSocket puede proceder de cualquier dirección URL, pero este código de ejemplo solo acepta
solicitudes de /ws .
Enviar y recibir mensajes
El método AcceptWebSocketAsync actualiza la conexión TCP a una conexión WebSocket y proporciona un objeto
WebSocket. Use el objeto WebSocket para enviar y recibir mensajes.
El código antes mostrado que acepta la solicitud WebSocket pasa el objeto WebSocket a un método Echo . El
código recibe un mensaje y devuelve inmediatamente el mismo mensaje. Los mensajes se envían y reciben en un
bucle hasta que el cliente cierra la conexión:
Cuando la conexión WebSocket se acepta antes de que el bucle comience, la canalización de middleware finaliza.
Tras cerrar el socket, se desenreda la canalización. Es decir, la solicitud deja de avanzar en la canalización cuando
WebSocket se acepta, pero cuando el bucle termina y el socket se cierra, la solicitud vuelve a recorrer la
canalización.
<system.webServer>
<webSocket enabled="false" />
</system.webServer>
Pasos siguientes
La aplicación de ejemplo que acompaña a este artículo es una aplicación de eco. Tiene una página web que
realiza las conexiones WebSocket y el servidor reenvía de vuelta al cliente todos los mensajes que reciba. Ejecute
la aplicación desde un símbolo del sistema (no está configurada para ejecutarse desde Visual Studio con IIS
Express) y vaya a http://localhost:5000. En la página web se muestra el estado de conexión en la parte superior
izquierda:
Seleccione Connect (Conectar) para enviar una solicitud WebSocket para la URL mostrada. Escriba un mensaje
de prueba y seleccione Send (Enviar). Cuando haya terminado, seleccione Close Socket (Cerrar socket). Los
informes de la sección Communication Log (Registro de comunicación) informan de cada acción de abrir,
enviar y cerrar a medida que se producen.
Metapaquete Microsoft.AspNetCore.App para
ASP.NET Core 2.1
19/06/2018 • 6 minutes to read • Edit Online
Esta característica requiere que ASP.NET Core 2.1 y versiones posteriores tengan como destino .NET Core 2.1 y
versiones posteriores.
El metapaquete Microsoft.AspNetCore.App para ASP.NET Core:
No incluye dependencias de terceros excepto Json.NET, Remotion.Linq e IX-Async. Estas dependencias de
terceros se consideran necesarias para garantizar el funcionamiento de las características de los principales
marcos.
Incluye todos los paquetes admitidos por el equipo de ASP.NET Core, excepto aquellos que contienen
dependencias de terceros (distintos de los mencionados anteriormente).
Incluye todos los paquetes admitidos por el equipo de Entity Framework Core, excepto aquellos que
contienen dependencias de terceros (distintos de los mencionados anteriormente).
Todas las características de ASP.NET Core 2.1 y versiones posteriores, así como de Entity Framework Core 2.1 y
versiones posteriores, están incluidas en el paquete Microsoft.AspNetCore.App . Las plantillas de proyecto
predeterminada destinadas a ASP.NET 2.1 y versiones posteriores usan este paquete. Se recomienda que las
aplicaciones que tengan como destino ASP.NET Core 2.1 y versiones posteriores, así como Entity Framework
Core 2.1 y versiones posteriores, usen el paquete Microsoft.AspNetCore.App .
El número de versión del metapaquete Microsoft.AspNetCore.App representa la versión de ASP.NET Core y la
versión de Entity Framework Core.
Mediante el metapaquete Microsoft.AspNetCore.App se proporcionan restricciones de versión que protegen la
aplicación:
Si se incluye un paquete que tiene una dependencia transitiva (no directa) en un paquete en
Microsoft.AspNetCore.App y los números de versión son distintos, NuGet generará un error.
Los demás paquetes agregados a la aplicación no pueden cambiar la versión de los paquetes que se incluyen
en Microsoft.AspNetCore.App .
La coherencia de versiones garantiza una experiencia fiable. Microsoft.AspNetCore.App se ha diseñado para
evitar las combinaciones de versiones no probadas de bits relacionados que se usan conjuntamente en la
misma aplicación.
Las aplicaciones que usan el metapaquete Microsoft.AspNetCore.App pueden aprovechar automáticamente el
marco de uso compartido de ASP.NET Core. Al usar el metapaquete Microsoft.AspNetCore.App , no se
implementa ningún recurso de los paquetes NuGet de ASP.NET Core a los que se hace referencia con la
aplicación, porque el marco de uso compartido de ASP.NET Core ya contiene esos recursos. Los recursos del
marco de uso compartido se precompilan para mejorar el tiempo de inicio de la aplicación. Para más
información, vea "Marco de uso compartido" en Empaquetado de distribución de .NET Core.
El siguiente archivo de proyecto .csproj hace referencia al metapaquete Microsoft.AspNetCore.App de ASP.NET
Core:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
</Project>
El marcado anterior representa una plantilla típica de ASP.NET Core 2.1 y versiones posteriores. No especifica
ningún número de versión para la referencia del paquete Microsoft.AspNetCore.App . Si no se especifica la
versión, el SDK define una versión implícita, es decir, Microsoft.NET.Sdk.Web . Es recomendable confiar en la
versión implícita especificada por el SDK y no establecer de forma explícita el número de versión en la referencia
del paquete. Puede dejar un comentario de GitHub en Discussion for the Microsoft.AspNetCore.App implicit
version (Debate sobre la versión implícita Microsoft.AspNetCore.App).
La versión implícita se establece en major.minor.0 para las aplicaciones portátiles. El mecanismo de puesta al
día del marco de uso compartido ejecutará la aplicación en la versión más reciente compatible entre los marcos
de uso compartidos instalados. Para garantizar que se use la misma versión en el desarrollo, las pruebas y la
producción, asegúrese de que en todos los entornos esté instalada la misma versión del marco de uso
compartido. Para las aplicaciones autocontenidas, el número de versión implícita se establece en el valor
major.minor.patch del marco de uso compartido incluido en el SDK instalado.
NOTE
Se recomienda que las aplicaciones que tengan como destino ASP.NET Core 2.1 y versiones posteriores usen
Microsoft.AspNetCore.App en lugar de este paquete. Consulte Migración desde Microsoft.AspNetCore.All a
Microsoft.AspNetCore.App en este artículo.
Esta característica requiere ASP.NET Core 2.x con .NET Core 2.x como destino.
El metapaquete Microsoft.AspNetCore.All para ASP.NET Core incluye lo siguiente:
Todos los paquetes admitidos por el equipo de ASP.NET Core.
Todos los paquetes admitidos por Entity Framework Core.
Dependencias internas y de terceros usadas por ASP.NET Core y Entity Framework Core.
Todas las características de ASP.NET Core 2.x y Entity Framework Core 2.x están incluidas en el paquete
Microsoft.AspNetCore.All . Las plantillas de proyecto predeterminada destinadas a ASP.NET 2.0 usan este
paquete.
El número de versión del metapaquete Microsoft.AspNetCore.All representa la versión de ASP.NET Core y la
versión de Entity Framework Core.
Las aplicaciones que usan el metapaquete Microsoft.AspNetCore.All pueden aprovechar automáticamente el
almacén en tiempo de ejecución de .NET Core. El almacén en tiempo de ejecución contiene todos los
recursos en tiempo de ejecución necesarios para ejecutar aplicaciones de ASP.NET Core 2.x. Al usar el
metapaquete Microsoft.AspNetCore.All , no se implementa ningún recurso de los paquetes NuGet de
ASP.NET Core a los que se hace referencia con la aplicación, porque el almacén en tiempo de ejecución de
.NET Core ya contiene esos recursos. Los recursos del almacén en tiempo de ejecución se precompilan para
mejorar el tiempo de inicio de la aplicación.
Puede usar el proceso de recorte de paquetes para quitar los paquetes que no se usan. Los paquetes
recortados se excluyen de la salida de la aplicación publicada.
El siguiente archivo .csproj hace referencia al metapaquete Microsoft.AspNetCore.All de ASP.NET Core:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
</ItemGroup>
</Project>
Con esta delineación de responsabilidades es más sencillo escalar la aplicación, porque resulta más fácil
codificar, depurar y probar algo (modelo, vista o controlador) que tenga un solo trabajo (y siga el principio de
responsabilidad única ). Es más difícil actualizar, probar y depurar código que tenga dependencias repartidas
entre dos o más de estas tres áreas. Por ejemplo, la lógica de la interfaz de usuario tiende a cambiar con mayor
frecuencia que la lógica de negocios. Si el código de presentación y la lógica de negocios se combinan en un solo
objeto, un objeto que contenga lógica de negocios deberá modificarse cada vez que cambie la interfaz de
usuario. A menudo esto genera errores y es necesario volver a probar la lógica de negocio después de cada
cambio mínimo en la interfaz de usuario.
NOTE
La vista y el controlador dependen del modelo. Sin embargo, el modelo no depende de la vista ni del controlador. Esta es
una de las ventajas principales de la separación. Esta separación permite compilar y probar el modelo con independencia
de la presentación visual.
NOTE
Hay muchas maneras de organizar el modelo en una aplicación que usa el patrón de arquitectura de MVC. Obtenga más
información sobre algunos tipos diferentes de tipos de modelo.
NOTE
Los controladores no deberían complicarse con demasiadas responsabilidades. Para evitar que la lógica del controlador se
vuelva demasiado compleja, use el principio de responsabilidad única para sacar la lógica de negocios fuera el controlador
y llevarla al modelo de dominio.
TIP
Si piensa que el controlador realiza con frecuencia los mismos tipos de acciones, puede seguir el principio Una vez y solo
una (DRY) moviendo estas acciones comunes a filtros.
Características
ASP.NET Core MVC incluye lo siguiente:
Enrutamiento
Enlace de modelos
Validación de modelos
Inserción de dependencias
Filtros
Áreas
API web
Capacidad de prueba
Motor de vistas de Razor
Vistas fuertemente tipadas
Aplicaciones auxiliares de etiquetas
Componentes de vista
Enrutamiento
ASP.NET Core MVC se basa en el enrutamiento de ASP.NET Core, un eficaz componente de asignación de URL
que permite compilar aplicaciones que tengan direcciones URL comprensibles y que admitan búsquedas. Esto
permite definir patrones de nomenclatura de URL de la aplicación que funcionen bien para la optimización del
motor de búsqueda (SEO ) y para la generación de vínculos, sin tener en cuenta cómo se organizan los archivos
en el servidor web. Puede definir las rutas mediante una sintaxis de plantilla de ruta adecuada que admita las
restricciones de valores de ruta, los valores predeterminados y los valores opcionales.
El enrutamiento basado en la convención permite definir globalmente los formatos de URL que acepta la
aplicación y cómo cada uno de esos formatos se asigna a un método de acción específico en un determinado
controlador. Cuando se recibe una solicitud entrante, el motor de enrutamiento analiza la URL y la hace coincidir
con uno de los formatos de URL definidos. Después, llama al método de acción del controlador asociado.
[Route("api/[controller]")]
public class ProductsController : Controller
{
[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
...
}
}
Enlace de modelos
El enlace de modelo de ASP.NET Core MVC convierte los datos de solicitud del cliente (valores de formulario,
datos de ruta, parámetros de cadena de consulta, encabezados HTTP ) en objetos que el controlador puede
controlar. Como resultado, la lógica del controlador no tiene que realizar el trabajo de pensar en los datos de la
solicitud entrante; simplemente tiene los datos como parámetros para los métodos de acción.
Validación de modelos
ASP.NET Core MVC admite la validación decorando el objeto de modelo con los atributos de validación de
anotación de datos. Los atributos de validación se comprueban en el lado cliente antes de que los valores se
registren en el servidor, y también en el servidor antes de llamar a la acción del controlador.
using System.ComponentModel.DataAnnotations;
public class LoginViewModel
{
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
El marco administra los datos de la solicitud de validación en el cliente y en el servidor. La lógica de validación
especificada en tipos de modelo se agrega a las vistas representadas como anotaciones discretas y se aplica en el
explorador con Validación de jQuery.
Inserción de dependencias
ASP.NET Core tiene compatibilidad integrada con la inserción de dependencias. En ASP.NET Core MVC, los
controladores pueden solicitar los servicios que necesiten a través de sus constructores, lo que les permite
seguir el principio de dependencias explícitas.
La aplicación también puede usar la inserción de dependencias en archivos de vistas, usando la directiva
@inject :
Filtros
Los filtros ayudan a los desarrolladores a encapsular ciertas preocupaciones transversales, como el control de
excepciones o la autorización. Los filtros habilitan la ejecución de lógica de procesamiento previo y posterior
para métodos de acción y pueden configurarse para ejecutarse en ciertos puntos dentro de la canalización de
ejecución para una solicitud determinada. Los filtros pueden aplicarse a controladores o acciones como atributos
(o pueden ejecutarse globalmente). En el marco se incluyen varios filtros (como Authorize ).
[Authorize]
public class AccountController : Controller
{
Áreas
Las áreas ofrecen una manera de dividir una aplicación web ASP.NET Core MVC de gran tamaño en
agrupaciones funcionales más pequeñas. Un área es una estructura de MVC dentro de una aplicación. En un
proyecto de MVC, los componentes lógicos como el modelo, el controlador y la vista se guardan en carpetas
diferentes, y MVC usa las convenciones de nomenclatura para crear la relación entre estos componentes. Para
una aplicación grande, puede ser conveniente dividir la aplicación en distintas áreas de funciones de alto nivel.
Por ejemplo, una aplicación de comercio electrónico con varias unidades de negocio, como la finalización de la
compra, la facturación, la búsqueda, etcétera. Cada una de estas unidades tiene sus propias vistas, controladores
y modelos de componentes lógicos.
API web
Además de ser una plataforma excelente para crear sitios web, ASP.NET Core MVC es muy compatible con la
creación de las API web. Puede crear servicios que lleguen con facilidad a una amplia gama de clientes, incluidos
exploradores y dispositivos móviles.
El marco de trabajo incluye compatibilidad para la negociación de contenido HTTP con la compatibilidad
integrada para dar formato a datos como JSON o XML. Escriba formateadores personalizados para agregar
compatibilidad con sus propios formatos.
Use la generación de vínculos para habilitar la compatibilidad con hipermedios. Habilite fácilmente la
compatibilidad con el uso compartido de recursos entre orígenes (CORS ) para que las API web se pueden
compartir entre varias aplicaciones web.
Capacidad de prueba
Uso del marco de trabajo de inserción de dependencias e interfaces hacer adecuadas a las pruebas unitarias y el
marco de trabajo incluye características (por ejemplo, un proveedor TestHost y InMemory para Entity
Framework) que hacen pruebas de integración rápido y fácil así. Obtenga más información sobre cómo probar
la lógica del controlador.
Motor de vistas de Razor
Las vistas de ASP.NET Core MVC usan el motor de vistas de Razor para representar vistas. Razor es un lenguaje
de marcado de plantillas compacto, expresivo y fluido para definir vistas que usan código incrustado de C#.
Razor se usa para generar dinámicamente contenido web en el servidor. Permite combinar de manera limpia el
código del servidor con el código y el contenido del lado cliente.
<ul>
@for (int i = 0; i < 5; i++) {
<li>List item @i</li>
}
</ul>
Con el motor de vistas de Razor, puede definir diseños, vistas parciales y secciones reemplazables.
Vistas fuertemente tipadas
Las vistas de Razor en MVC pueden estar fuertemente tipadas en función del modelo. Los controladores
pueden pasar un modelo fuertemente tipado a las vistas para que estas admitan IntelliSense y la comprobación
de tipos.
Por ejemplo, en esta vista se representa un modelo de tipo IEnumerable<Product> :
@model IEnumerable<Product>
<ul>
@foreach (Product p in Model)
{
<li>@p.Name</li>
}
</ul>
<p>
Thank you for confirming your email.
Please <a asp-controller="Account" asp-action="Login">Click here to Log in</a>.
</p>
La aplicación auxiliar de etiquetas EnvironmentTagHelper puede usarse para incluir distintos scripts en las vistas
(por ejemplo, sin formato o reducida) según el entorno en tiempo de ejecución, como desarrollo, ensayo o
producción:
<environment names="Development">
<script src="~/lib/jquery/dist/jquery.js"></script>
</environment>
<environment names="Staging,Production">
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.1.4.min.js"
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery">
</script>
</environment>
Las aplicaciones auxiliares de etiquetas ofrecen una experiencia de desarrollo compatible con HTML y un
entorno de IntelliSense enriquecido para crear formato HTML y Razor. La mayoría de las aplicaciones auxiliares
de etiquetas integradas tienen como destino elementos HTML existentes y proporcionan atributos del lado
servidor para el elemento.
Componentes de vista
Los componentes de vista permiten empaquetar la lógica de representación y reutilizarla en toda la aplicación.
Son similares a las vistas parciales, pero con lógica asociada.
Enlace de modelos en ASP.NET Core
25/06/2018 • 15 minutes to read • Edit Online
Tipos de referencia: el enlace crea una instancia de una clase con el constructor
predeterminado sin tener que configurar propiedades. Pero el enlace de modelos establece
parámetros string en null .
Tipos que aceptan valores NULL: estos tipos se establecen en null . En el ejemplo anterior,
el enlace de modelos establece id en null ya que es de tipo int? .
Tipos de valor: los tipos de valor que no aceptan valores NULL de tipo T se establecen en
default(T) . Por ejemplo, el enlace de modelos establecerá un parámetro int id en 0.
Considere la posibilidad de usar la validación de modelos o los tipos que aceptan valores
NULL en lugar de depender de valores predeterminados.
Si se produce un error de enlace, MVC no genera un error. Todas las acciones que aceptan datos
del usuario deben comprobar la propiedad ModelState.IsValid .
Nota: Cada entrada en la propiedad ModelState del controlador es un elemento ModelStateEntry
que contiene una propiedad Errors . No suele ser necesario consultar esta colección por su cuenta.
Utilice ModelState.IsValid en su lugar.
Además, hay algunos tipos de datos especiales que MVC debe tener en cuenta al realizar el enlace
de modelos:
IFormFile , IEnumerable<IFormFile> : uno o más archivos cargados que forman parte de la
solicitud HTTP.
CancellationToken : se usa para cancelar la actividad en controladores asincrónicos.
NOTE
Puede haber como máximo un parámetro por cada acción decorada con [FromBody] . El tiempo de
ejecución de ASP.NET Core MVC delega la responsabilidad de leer la secuencia de solicitudes al formateador.
Una vez que se lee la secuencia de solicitudes para un parámetro, por lo general no es posible leer la
secuencia de solicitudes de nuevo para enlazar otros parámetros [FromBody] .
NOTE
JsonInputFormatter es el formateador predeterminado y se basa en Json.NET.
Atributos de validación
Los atributos de validación son una forma de configurar la validación del modelo, por lo que
conceptualmente es similar a la validación en campos de tablas de base de datos. Esto incluye las
restricciones, como la asignación de tipos de datos o los campos obligatorios. Entre otros tipos de
validación se encuentran el de aplicar patrones a datos para aplicar reglas de negocio, como tarjetas
de crédito, números de teléfono o direcciones de correo electrónico. Con los atributos de validación,
es mucho más sencillo aplicar estos requisitos y son más fáciles de usar.
Aquí mostramos un modelo Movie anotado desde una aplicación que almacena información sobre
películas y programas de TV. La mayoría de las propiedades son obligatorias y varias propiedades de
cadena tienen requisitos de longitud. Además, hay una restricción de rango numérico para la
propiedad Price de 0 a 999,99 $, junto con un atributo de validación personalizado.
public class Movie
{
public int Id { get; set; }
[Required]
[StringLength(100)]
public string Title { get; set; }
[ClassicMovie(1960)]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[Required]
[StringLength(1000)]
public string Description { get; set; }
[Range(0, 999.99)]
public decimal Price { get; set; }
[Required]
public Genre Genre { get; set; }
Con solo leer el modelo se pueden detectar las reglas sobre los datos para esta aplicación, lo que
facilita mantener el código. Aquí tenemos varios atributos de validación integradas conocidos:
[CreditCard] : valida que la propiedad tiene formato de tarjeta de crédito.
[Compare] : valida dos propiedades en una coincidencia de modelos.
[EmailAddress] : valida que la propiedad tiene formato de correo electrónico.
[Phone] : valida que la propiedad tiene formato de teléfono.
[Range] : valida que el valor de propiedad se encuentra dentro del intervalo especificado.
[RegularExpression] : valida que los datos coinciden con la expresión regular especificada.
[Required] : hace que una propiedad sea obligatoria.
[StringLength] : valida que una propiedad de cadena tiene como mucho la longitud máxima
determinada.
[Url] : valida que la propiedad tiene un formato de URL.
Validación manual
Después de completarse la validación y el enlace de modelos, es posible que quiera repetir partes de
estos procesos. Por ejemplo, puede que un usuario haya escrito texto en un campo esperando recibir
un entero, o puede que necesite calcular un valor para la propiedad del modelo.
Es posible que tenga que ejecutar manualmente la validación. Para ello, llame al método
TryValidateModel , como se muestra aquí:
TryValidateModel(movie);
Validación personalizada
Los atributos de validación funcionan para la mayoría de las necesidades validación. Pero algunas
reglas de validación son específicas de su negocio. Es posible que las reglas no sean técnicas de
validación de datos comunes, como asegurarse de que un campo es obligatorio o se ajusta a un
rango de valores. Para estos escenarios, los atributos de validación personalizados son una excelente
solución. Es muy fácil crear sus propios atributos de validación personalizados en MVC. Solo tiene
que heredar de ValidationAttribute e invalidar el método IsValid . El método IsValid acepta dos
parámetros: el primero es un objeto denominado value y el segundo es un objeto ValidationContext
denominado validationContext. El objeto value hace referencia al valor real del campo que va a
validar el validador personalizado.
En el ejemplo siguiente, una regla de negocio indica que los usuarios no pueden especificar el género
Classic para una película estrenada después de 1960. El atributo [ClassicMovie] comprueba primero
el género y, si es un clásico, comprueba que la fecha de estreno es posterior a 1960. Si se estrenó
después de 1960, se produce un error de validación. El atributo acepta un parámetro de entero que
representa el año que puede usar para validar los datos. Puede capturar el valor del parámetro en el
constructor del atributo, como se muestra aquí:
return ValidationResult.Success;
}
La variable movie anterior representa un objeto Movie que contiene los datos del envío del
formulario para validarlos. En este caso, el código de validación comprueba la fecha y el género en el
método IsValid de la clase ClassicMovieAttribute según las reglas. Después de validar
correctamente IsValid , devuelve un código ValidationResult.Success y, cuando se produce un error
de validación, un ValidationResult con un mensaje de error. Cuando un usuario modifica el campo
Genre y envía el formulario, el método IsValid de ClassicMovieAttribute comprueba si la película
es un clásico. Al igual que con cualquier atributo integrado, aplique ClassicMovieAttribute a una
propiedad como ReleaseDate para asegurarse de que se produce la validación, tal como se muestra
en el ejemplo de código anterior. Puesto que el ejemplo solo funciona con tipos Movie , una mejor
opción es usar IValidatableObject tal y como se muestra en el párrafo siguiente.
Si lo prefiere, puede colocar este mismo código en el modelo, implementando el método Validate
en la interfaz IValidatableObject . Aunque los atributos de validación personalizada funcionan bien
para validar propiedades individuales, puede implementar IValidatableObject para implementar la
validación de nivel de clase como se ve aquí.
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Genre == Genre.Classic && ReleaseDate.Year > _classicYear)
{
yield return new ValidationResult(
$"Classic movies must have a release year earlier than {_classicYear}.",
new[] { "ReleaseDate" });
}
}
<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.2.0.min.js"></script>
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.16.0/jquery.validate.min.js">
</script>
<script
src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.6/jquery.validate.unobtrus
ive.min.js"></script>
El script Validación discreta de jQuery es una biblioteca front-end personalizada de Microsoft que se
basa en el conocido complemento Validación de jQuery. Si no usa Validación discreta de jQuery,
deberá codificar la misma lógica de validación dos veces: una vez en los atributos de validación del
lado servidor en las propiedades del modelo y luego en los scripts del lado cliente (los ejemplos del
método validate() de Validación de jQuery muestran lo complejo que podría resultar). En su lugar,
las aplicaciones auxiliares de etiquetas y las aplicaciones auxiliares de HTML de MVC pueden usar
los atributos de validación y escribir metadatos de las propiedades del modelo para representar
atributos de datos HTML 5 en los elementos de formulario que necesitan validación. MVC genera los
atributos data- para los atributos integrados y los personalizados. La Validación discreta de jQuery
analiza después los atributos data- y pasa la lógica a Validación de jQuery. De este modo, la lógica
de validación del lado servidor se "copia" de manera eficaz en el cliente. Puede mostrar errores de
validación en el cliente usando las aplicaciones auxiliares de etiquetas relevantes, como se muestra
aquí:
<div class="form-group">
<label asp-for="ReleaseDate" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="ReleaseDate" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger"></span>
</div>
</div>
Las anteriores aplicaciones auxiliares de etiqueta representan el código HTML siguiente. Tenga en
cuenta que los atributos data- en los resultados HTML corresponden a los atributos de validación
para la propiedad ReleaseDate . El atributo data-val-required siguiente contiene un mensaje de
error que se muestra si el usuario no rellena el campo de fecha de estreno. La Validación discreta de
jQuery pasa este valor al método required() de la Validación de jQuery, que muestra un mensaje en
el elemento <span> que lo acompaña.
La validación del lado cliente impide realizar el envío hasta que el formulario sea válido. El botón
Enviar ejecuta JavaScript para enviar el formulario o mostrar mensajes de error.
MVC determina los valores de atributo de tipo según el tipo de datos de .NET de una propiedad,
posiblemente invalidada usando los atributos [DataType] . El atributo [DataType] de base no realiza
ninguna validación en el lado servidor. Los exploradores eligen sus propios mensajes de error y
muestran estos errores cuando quieren, si bien el paquete Validación discreta de jQuery puede
invalidar esos mensajes y mostrarlos de manera coherente con otros. Esto es más evidente cuando
los usuarios aplican subclases de [DataType] , como por ejemplo, [EmailAddress] .
Agregar validación a formularios dinámicos
Como la Validación discreta de jQuery pasa la lógica de validación a Validación de jQuery cuando la
página se carga por primera vez, los formularios generados dinámicamente no exhiben la validación
automáticamente. En su lugar, hay que indicar a Validación discreta de jQuery que analice el
formulario dinámico inmediatamente después de crearlo. Por ejemplo, en este código se muestra
cómo es posible configurar la validación del lado cliente en un formulario agregado mediante AJAX.
$.get({
url: "https://url/that/returns/a/form",
dataType: "html",
error: function(jqXHR, textStatus, errorThrown) {
alert(textStatus + ": Couldn't add form. " + errorThrown);
},
success: function(newFormHTML) {
var container = document.getElementById("form-container");
container.insertAdjacentHTML("beforeend", newFormHTML);
var forms = container.getElementsByTagName("form");
var newForm = forms[forms.length - 1];
$.validator.unobtrusive.parse(newForm);
}
})
$.get({
url: "https://url/that/returns/a/control",
dataType: "html",
error: function(jqXHR, textStatus, errorThrown) {
alert(textStatus + ": Couldn't add control. " + errorThrown);
},
success: function(newInputHTML) {
var form = document.getElementById("my-form");
form.insertAdjacentHTML("beforeend", newInputHTML);
$(form).removeData("validator") // Added by jQuery Validate
.removeData("unobtrusiveValidation"); // Added by jQuery Unobtrusive Validation
$.validator.unobtrusive.parse(form);
}
})
IClientModelValidator
Puede crear lógica del lado cliente para el atributo personalizado y Validación discreta, que crea un
adaptador para la validación de jQuery, la ejecutará en el cliente automáticamente como parte de la
validación. El primer paso consiste en controlar qué atributos de datos se agregan mediante la
implementación de la interfaz IClientModelValidator , como se muestra aquí:
Los atributos que implementan esta interfaz pueden agregar atributos HTML a los campos
generados. Al examinar la salida del elemento ReleaseDate muestra que el HTML es similar al
ejemplo anterior, excepto que ahora hay un atributo data-val-classicmovie que se definió en el
método AddValidation de IClientModelValidator .
La validación discreta usa los datos en los atributos data- para mostrar mensajes de error. Pero
jQuery no sabe nada de reglas o mensajes hasta que estas se agregan al objeto validator de
jQuery. Esto se muestra en el ejemplo siguiente, que agrega un método denominado classicmovie
que contiene código de validación de cliente personalizada para el objeto validator de jQuery. Aquí
encontrará una explicación del método unobtrusive.adapters.add.
$(function () {
$.validator.addMethod('classicmovie',
function (value, element, params) {
// Get element value. Classic genre has value '0'.
var genre = $(params[0]).val(),
year = params[1],
date = new Date(value);
if (genre && genre.length > 0 && genre[0] === '0') {
// Since this is a classic movie, invalid if release date is after given year.
return date.getFullYear() <= year;
}
return true;
});
$.validator.unobtrusive.adapters.add('classicmovie',
['year'],
function (options) {
var element = $(options.form).find('select#Genre')[0];
options.rules['classicmovie'] = [element, parseInt(options.params['year'])];
options.messages['classicmovie'] = options.message;
});
});
Ahora jQuery tiene información para ejecutar la validación personalizada de JavaScript, así como el
mensaje de error que se debe mostrar si ese código de validación devuelve false.
Validación remota
La validación remota es una característica excelente que se usa cuando necesita validar los datos del
cliente con datos del servidor. Por ejemplo, puede que la aplicación necesite comprobar si ya se está
usando un nombre de usuario o una dirección de correo, y debe consultar una gran cantidad de
datos para hacerlo. El hecho de descargar grandes conjuntos de datos para validar un campo o unos
pocos campos consume demasiados recursos. También podría exponer información confidencial.
Una alternativa a esto consiste en realizar una solicitud de ida y vuelta para validar un campo.
Puede implementar la validación remota en un proceso de dos pasos. Primero, debe anotar el
modelo con el atributo [Remote] . El atributo [Remote] acepta varias sobrecargas que puede usar
para dirigir el JavaScript del lado cliente al código adecuado al que se debe llamar. El ejemplo
siguiente señala al método de acción VerifyEmail del controlador Users .
La respuesta del lado servidor debe ser una cadena JSON que debe ser "true" para elementos
válidos y puede ser "false" , undefined o null para elementos no válidos, usando el mensaje
de error predeterminado. Si la respuesta del lado servidor es una cadena, como por ejemplo,
"That name is already taken, try peter123 instead" , dicha cadena se mostrará como un mensaje
de error personalizado en lugar del predeterminado.
La definición del método VerifyEmail() sigue estas reglas, tal y como se muestra abajo. Devuelve un
mensaje de error de validación si la dirección de correo ya existe o muestra true si la dirección de
correo está disponible, y ajusta el resultado en un objeto JsonResult . Después, el lado cliente puede
usar el valor devuelto para continuar o mostrar el error si es necesario.
[AcceptVerbs("Get", "Post")]
public IActionResult VerifyEmail(string email)
{
if (!_userRepository.VerifyEmail(email))
{
return Json($"Email {email} is already in use.");
}
return Json(true);
}
Cuando los usuarios escriben una dirección de correo, JavaScript en la vista hace una llamada
remota para comprobar si esa dirección ya existe y, de ser así, se muestra el mensaje de error. En caso
contrario, el usuario puede enviar el formulario como de costumbre.
La propiedad AdditionalFields del atributo [Remote] es útil para validar combinaciones de campos
con los datos del servidor. Por ejemplo, si el modelo User anterior tenía dos propiedades adicionales
llamadas FirstName y LastName , recomendamos comprobar que ningún usuario actual tenga ya ese
par de nombres. Tendrá que definir las nuevas propiedades como se muestra en el código siguiente:
[AcceptVerbs("Get", "Post")]
public IActionResult VerifyName(string firstName, string lastName)
{
if (!_userRepository.VerifyName(firstName, lastName))
{
return Json(data: $"A user named {firstName} {lastName} already exists.");
}
Cuando los usuarios escriben su nombre y sus apellidos, JavaScript hace lo siguiente:
Realiza una llamada remota para comprobar si ese par de nombres ya se está usando.
Si el par de nombres ya existe, se muestra un mensaje de error.
Si está disponible, el usuario puede enviar el formulario.
Si necesita validar dos o más campos adicionales con el atributo [Remote] , proporciónelos como una
lista delimitada por comas. Por ejemplo, para agregar una MiddleName propiedad en el modelo,
establezca el [Remote] atributo tal como se muestra en el código siguiente:
[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName) + "," +
nameof(LastName))]
public string MiddleName { get; set; }
AdditionalFields , al igual que todos los argumentos de atributo, debe ser una expresión constante.
Por lo tanto, no se debe usar una cadena interpolada ni llamar a string.Join() para inicializar
AdditionalFields . Para cada campo adicional que agregue al atributo [Remote] , debe agregar otro
argumento al método de acción de controlador correspondiente.
Vistas de ASP.NET Core MVC
25/06/2018 • 27 minutes to read • Edit Online
El controlador Home está representado por una carpeta Home situada dentro de la carpeta Views. La carpeta
Home contiene las vistas correspondientes a las páginas web About, Contact e Index (página principal). Cuando
un usuario solicita una de estas tres páginas web, las acciones del controlador Home determinan cuál de las tres
vistas se usa para crear y devolver una página web al usuario.
Use diseños para cohesionar las secciones de la página web y reducir la repetición del código. Los diseños
suelen contener encabezado, elementos de menú y navegación y pie de página. El encabezado y el pie de página
suelen contener marcado reutilizable para muchos elementos de metadatos y vínculos a recursos de script y
estilo. Los diseños ayudan a evitar este marcado reutilizable en las vistas.
Las vistas parciales administran las partes reutilizables de las vistas para reducir la duplicación de código. Por
ejemplo, resulta útil usar una vista parcial en la biografía de un autor que aparece en varias vistas de un sitio
web de blog. Una biografía de autor es contenido de vista normal y no requiere que se ejecute código para
generar el contenido de la página web. El contenido de la biografía del autor está disponible para la vista
simplemente mediante un enlace de modelos, por lo que resulta ideal usar una vista parcial para este tipo de
contenido.
Los componentes de la vista se asemejan a las vistas parciales en que permiten reducir el código repetitivo, pero
son adecuados para ver contenido que requiere la ejecución de código en el servidor con el fin de representar la
página web. Los componentes de la vista son útiles cuando el contenido representado requiere una interacción
con la base de datos, por ejemplo, en el carro de la compra de un sitio web. Los componentes de la vista no se
limitan a un enlace de modelos para generar la salida de la página web.
Ventajas de utilizar las vistas
Las vistas separan el marcado de la interfaz de usuario de otras partes de la aplicación con el fin de ayudar a
establecer una separación de intereses (SoC ) dentro de una aplicación MVC. Diseñar respetando el principio
SoC hace que la aplicación sea modular, lo que ofrece varias ventajas:
La aplicación es más fácil de mantener, ya que está mejor organizada. Las vistas generalmente se agrupan
por característica de la aplicación. Esto facilita la búsqueda de vistas relacionadas cuando se trabaja en una
característica.
Las partes de la aplicación están acopladas de forma ligera. Las vistas de la aplicación se pueden compilar y
actualizar por separado de los componentes de la lógica de negocios y el acceso a datos. Las vistas de la
aplicación se pueden modificar sin necesidad de tener que actualizar otras partes de la aplicación.
Es más fácil probar los elementos de la interfaz de usuario de la aplicación, ya que las vistas son unidades
independientes.
Debido a una mejor organización, es menos probable que se repitan accidentalmente secciones de la interfaz
de usuario.
@{
ViewData["Title"] = "About";
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>
El marcado de Razor comienza con el símbolo @ . Ejecute instrucciones de C# mediante la colocación de código
C# en los bloques de código Razor activados por llaves ( { ... } ). Por ejemplo, vea la asignación de "About" en
ViewData["Title"] mostrada anteriormente. Para mostrar valores en HTML, simplemente haga referencia al
valor con el símbolo @ . Ver el contenido de los elementos <h2> y <h3> anteriores.
El contenido de la vista mostrado anteriormente es solo una parte de toda la página web que se presenta al
usuario. El resto del diseño de la página y otros aspectos comunes de la vista se especifican en otros archivos de
vista. Para obtener más información, consulte el tema Diseño.
return View();
}
Cuando esta acción devuelve un resultado, la vista About.cshtml mostrada en la última sección se representa
como la página web siguiente:
return View("Orders");
return View(Orders);
Detección de vista
Cuando una acción devuelve una vista, tiene lugar un proceso llamado detección de vista. Este proceso
determina qué archivo de vista se utiliza en función del nombre de la vista.
El comportamiento predeterminado del método View ( return View(); ) es devolver una vista con el mismo
nombre que el método de acción desde el que se llama. Por ejemplo, el nombre de método About ActionResult
del controlador se usa para buscar un archivo de vista denominado About.cshtml. En primer lugar, el runtime
busca la vista en la carpeta Views/[nombreDelControlador ]. Si no encuentra una vista que coincida, busca la
vista en la carpeta Shared.
Da igual si se devuelve implícitamente ViewResult con return View(); o si se pasa explícitamente el nombre
de la vista al método View con return View("<ViewName>"); . En ambos casos, la detección de vista busca un
archivo de vista coincidente en este orden:
1. Views/[nombreDeControlador ]/[nombreDeVista ].cshtml
2. Views/Shared/[nombreDeVista ].cshtml
En lugar del nombre de una vista, se puede proporcionar la ruta de acceso del archivo de vista. Si se utiliza una
ruta de acceso absoluta que comience en la raíz de la aplicación (también puede empezar con "/" o "/"), debe
especificarse la extensión .cshtml:
return View("Views/Home/About.cshtml");
También se puede usar una ruta de acceso relativa para especificar vistas de directorios distintos sin la extensión
.cshtml. Dentro de HomeController , se puede devolver la vista Index de las vistas Manage con una ruta de
acceso relativa:
return View("../Manage/Index");
De forma similar, se puede indicar el directorio específico del controlador actual con el prefijo "./":
return View("./About");
Las vistas parciales y los componentes de vista usan mecanismos de detección similares (aunque no idénticos).
Puede personalizar la convención predeterminada para la localización de las vistas en la aplicación si utiliza una
interfaz IViewLocationExpander personalizada.
La detección de vistas se basa en la búsqueda de archivos de vista por nombre de archivo. Si el sistema de
archivos subyacente distingue mayúsculas de minúsculas, es probable que también lo hagan los nombres de las
vistas. Para que haya compatibilidad entre sistemas operativos, haga coincidir las letras mayúsculas y
minúsculas de los nombres de controlador y acción con los nombres de archivo y carpetas de vista asociados. Si
mientras trabaja con un sistema de archivos que distingue mayúsculas de minúsculas se produce un error que
le indica que no se encuentra un archivo de vista, confirme que el archivo de vista solicitado y el nombre de
archivo de vista real coinciden en el uso de mayúsculas.
Siga el procedimiento recomendado de organizar la estructura de archivos de vistas de modo que refleje las
relaciones existentes entre controladores, acciones y vistas para una mayor claridad y un mantenimiento más
sencillo.
@model WebApplication1.ViewModels.Address
<h2>Contact</h2>
<address>
@Model.Street<br>
@Model.City, @Model.State @Model.PostalCode<br>
<abbr title="Phone">P:</abbr> 425.555.0100
</address>
return View(viewModel);
}
No hay ninguna restricción sobre los tipos de modelo que se pueden proporcionar a una vista. Se recomienda
usar modelos de vista POCO (objeto CRL estándar) con poco o ningún comportamiento (métodos) definido.
Por lo general, las clases de modelo de vista se almacenan en la carpeta Models o en otra carpeta ViewModels
en la raíz de la aplicación. El modelo de vista Address usado en el ejemplo anterior es un modelo de vista POCO
almacenado en un archivo denominado Address.cs:
namespace WebApplication1.ViewModels
{
public class Address
{
public string Name { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string PostalCode { get; set; }
}
}
Nada le impide usar las mismas clases tanto para los tipos de modelo de vista como para los de modelo de
negocio. Pero el uso de modelos independientes permite que las vistas varíen de forma independiente de las
partes de lógica de negocios y acceso de datos de la aplicación. La separación de los modelos y los modelos de
vista también ofrece ventajas de seguridad cuando los modelos utilizan enlace de modelo y validación para los
datos enviados a la aplicación por el usuario.
Datos con establecimiento flexible de tipos (ViewData, atributo ViewData y ViewBag)
ViewBag no está disponible en las páginas de Razor.
Además de las vistas fuertemente tipadas, las vistas tienen acceso a una colección de datos débilmente tipada
(también denominada imprecisa). A diferencia de los tipos fuertes, en los tipos débiles (o débilmente tipados) no
se declara explícitamente el tipo de datos que se está utilizando. Puede usar la colección de datos débilmente
tipada para introducir y sacar pequeñas cantidades de datos de los controladores y las vistas.
Una vista y una vista de diseño Establecer el contenido del elemento <title> en la vista de
diseño de un archivo de vista.
Una vista parcial y una vista Un widget que muestra datos basados en la página web que
el usuario solicitó.
Puede hacer referencia a esta colección a través de las propiedades ViewData o ViewBag en controladores y
vistas. La propiedad ViewData es un diccionario de objetos débilmente tipados. La propiedad ViewBag es un
contenedor alrededor de ViewData que proporciona propiedades dinámicas para la colección ViewData
subyacente.
ViewData y ViewBag se resuelven de forma dinámica en tiempo de ejecución. Debido a que no ofrecen la
comprobación de tipos en tiempo de compilación, ambas son generalmente más propensas a errores que el uso
de un modelo de vista. Por esta razón, algunos desarrolladores prefieren prescindir de ViewData y ViewBag o
usarlos lo menos posible.
ViewData
ViewData es un objeto ViewDataDictionary al que se accede a través de claves string . Los datos de cadena se
pueden almacenar y utilizar directamente sin necesidad de una conversión, pero primero debe convertir otros
valores de objeto ViewData a tipos específicos cuando los extrae. Se puede usar ViewData para pasar datos de
los controladores a las vistas y dentro de las vistas, incluidas las vistas parciales y los diseños.
En el ejemplo siguiente se usa ViewData en una acción para establecer los valores de saludo y dirección:
return View();
}
@ViewData["Greeting"] World!
<address>
@address.Name<br>
@address.Street<br>
@address.City, @address.State @address.PostalCode
</address>
Atributo ViewData
Otro método en el que se usa ViewDataDictionary es ViewDataAttribute. Los valores de las propiedades de
controladores o modelos de página de Razor completadas con [ViewData] se almacenan y cargan desde el
diccionario.
En el siguiente ejemplo, el controlador Home contiene una propiedad Title completada con [ViewData] . El
método About establece el título de la vista About:
return View();
}
}
<h1>@Model.Title</h1>
<!DOCTYPE html>
<html lang="en">
<head>
<title>@ViewData["Title"] - WebApplication</title>
...
ViewBag
ViewBag no está disponible en las páginas de Razor.
ViewBag es un objeto DynamicViewData que proporciona acceso dinámico a los objetos almacenados en
ViewData . ViewBag puede ser más cómodo de trabajar con él, ya que no requiere conversión. En el ejemplo
siguiente se muestra cómo usar ViewBag con el mismo resultado que al usar ViewData anteriormente:
public IActionResult SomeAction()
{
ViewBag.Greeting = "Hello";
ViewBag.Address = new Address()
{
Name = "Steve",
Street = "123 Main St",
City = "Hudson",
State = "OH",
PostalCode = "44236"
};
return View();
}
@ViewBag.Greeting World!
<address>
@ViewBag.Address.Name<br>
@ViewBag.Address.Street<br>
@ViewBag.Address.City, @ViewBag.Address.State @ViewBag.Address.PostalCode
</address>
Establezca el título con ViewBag y la descripción con ViewData en la parte superior de una vista About.cshtml:
@{
Layout = "/Views/Shared/_Layout.cshtml";
ViewBag.Title = "About Contoso";
ViewData["Description"] = "Let us tell you about Contoso's philosophy and mission.";
}
Lea las propiedades pero invierta el uso de ViewData y ViewBag . En el archivo _Layout.cshtml, obtenga el título
con ViewData y la descripción con ViewBag :
<!DOCTYPE html>
<html lang="en">
<head>
<title>@ViewData["Title"]</title>
<meta name="description" content="@ViewBag.Description">
...
Recuerde que las cadenas no requieren una conversión para ViewData . Puede usar @ViewData["Title"] sin
conversión alguna.
Es posible utilizar ViewData y ViewBag al mismo tiempo, al igual que combinar la lectura y escritura de
propiedades. Se representa el marcado siguiente:
<!DOCTYPE html>
<html lang="en">
<head>
<title>About Contoso</title>
<meta name="description" content="Let us tell you about Contoso's philosophy and mission.">
...
<address>
@Model.Street<br>
@Model.City, @Model.State @Model.PostalCode<br>
<abbr title="Phone">P:</abbr> 425.555.0100
</address>
Representación de HTML
El lenguaje de Razor predeterminado es HTML. Representar el HTML del marcado de Razor no difiere mucho de
representar el HTML de un archivo HTML. El marcado HTML de los archivos de Razor .cshtml se representa en
el servidor sin cambios.
Sintaxis de Razor
Razor admite C# y usa el símbolo @ para realizar la transición de HTML a C#. Razor evalúa las expresiones de
C# y las representa en la salida HTML.
Cuando el símbolo @ va seguido de una palabra clave reservada de Razor, realiza una transición a un marcado
específico de Razor; en caso contrario, realiza la transición a C# simple.
Para hacer escape en un símbolo @ en el marcado de Razor, use un segundo símbolo @ :
<p>@@Username</p>
<p>@Username</p>
El contenido y los atributos HTML que tienen direcciones de correo electrónico no tratan el símbolo @ como un
carácter de transición. El análisis de Razor no se detiene en las direcciones de correo electrónico del siguiente
ejemplo:
<a href="mailto:[email protected]">[email protected]</a>
<p>@DateTime.Now</p>
<p>@DateTime.IsLeapYear(2016)</p>
Con la excepción de la palabra clave de C# await , las expresiones implícitas no deben contener espacios. Si la
instrucción de C# tiene un final claro, se pueden entremezclar espacios:
<p>@await DoSomething("hello", "world")</p>
Las expresiones implícitas no pueden contener tipos genéricos de C#, ya que los caracteres dentro de los
corchetes ( <> ) se interpretan como una etiqueta HTML. El siguiente código no es válido:
<p>@GenericMethod<int>()</p>
El código anterior genera un error del compilador similar a uno de los siguientes:
El elemento "int" no estaba cerrado. Todos los elementos deben ser de autocierre o tener una etiqueta de fin
coincidente.
No se puede convertir el grupo de métodos "GenericMethod" en el tipo no delegado "object". ¿Intentó
invocar el método?
Las llamadas a método genéricas deben estar incluidas en una expresión de Razor explícita o en un bloque de
código de Razor.
Se pueden usar expresiones explícitas para concatenar texto con un resultado de la expresión:
@{
var joe = new Person("Joe", 33);
}
<p>Age@(joe.Age)</p>
Sin la expresión explícita, <p>[email protected]</p> se trataría como una dirección de correo electrónico y se mostraría
como <p>[email protected]</p> . Pero, si se escribe como una expresión explícita, se representa <p>Age33</p> .
Se pueden usar expresiones explícitas para representar la salida de métodos genéricos en los archivos .cshtml. En
el siguiente marcado se muestra cómo corregir el error mostrado anteriormente provocado por el uso de
corchetes en un código C# genérico. El código se escribe como una expresión explícita:
<p>@(GenericMethod<int>())</p>
Codificación de expresiones
Las expresiones de C# que se evalúan como una cadena están codificadas en HTML. Las expresiones de C# que
se evalúan como IHtmlContent se representan directamente a través de IHtmlContent.WriteTo . Las expresiones
de C# que no se evalúan como IHtmlContent se convierten en una cadena por medio de ToString y se codifican
antes de que se representen.
@("<span>Hello World</span>")
<span>Hello World</span>
<span>Hello World</span>
WARNING
Usar HtmlHelper.Raw en una entrada de usuario no saneada constituye un riesgo de seguridad. Dicha entrada podría
contener código JavaScript malintencionado u otras vulnerabilidades de seguridad. Sanear una entrada de usuario es
complicado. Evite usar HtmlHelper.Raw con entradas de usuario.
@Html.Raw("<span>Hello World</span>")
<span>Hello World</span>
@{
var quote = "The future depends on what you do today. - Mahatma Gandhi";
}
<p>@quote</p>
@{
quote = "Hate cannot drive out hate, only love can do that. - Martin Luther King, Jr.";
}
<p>@quote</p>
Transiciones implícitas
El lenguaje predeterminado de un bloque de código es C#, pero la página de Razor puede volver al HTML:
@{
var inCSharp = true;
<p>Now in HTML, was in C# @inCSharp</p>
}
Emplee este método para representar HTML que no esté insertado entre etiquetas HTML. Sin una etiqueta
HTML o de Razor, se produce un error de tiempo de ejecución de Razor.
La etiqueta <text> es útil para controlar el espacio en blanco al representar el contenido:
Solo se representa el contenido entre etiquetas <text>.
En la salida HTML no hay espacios en blanco antes o después de la etiqueta <text>.
Transición de línea explícita con @:
Para representar el resto de una línea completa como HTML dentro de un bloque de código, use la sintaxis @: :
Estructuras de control
Las estructuras de control son una extensión de los bloques de código. Todos los aspectos de los bloques de
código (transición a marcado, C# en línea) son válidos también en las siguientes estructuras:
Los condicionales @if, else if, else y @switch
@if controla cuándo se ejecuta el código:
@if (value % 2 == 0)
{
<p>The value was even.</p>
}
@if (value % 2 == 0)
{
<p>The value was even.</p>
}
else if (value >= 1337)
{
<p>The value is large.</p>
}
else
{
<p>The value is odd and small.</p>
}
@switch (value)
{
case 1:
<p>The value is 1!</p>
break;
case 1337:
<p>Your number is 1337!</p>
break;
default:
<p>Your number wasn't 1 or 1337.</p>
break;
}
@{
var people = new Person[]
{
new Person("Weston", 33),
new Person("Johnathon", 41),
...
};
}
@while
@{ var i = 0; }
@while (i < people.Length)
{
var person = people[i];
<p>Name: @person.Name</p>
<p>Age: @person.Age</p>
i++;
}
@do while
@{ var i = 0; }
@do
{
var person = people[i];
<p>Name: @person.Name</p>
<p>Age: @person.Age</p>
i++;
} while (i < people.Length);
@using (Html.BeginForm())
{
<div>
email:
<input type="email" id="Email" value="">
<button>Register</button>
</div>
}
Con las aplicaciones auxiliares de etiquetas se pueden realizar acciones de nivel de ámbito.
@try, catch, finally
El control de excepciones es similar a C#:
@try
{
throw new InvalidOperationException("You did something invalid.");
}
catch (Exception ex)
{
<p>The exception message: @ex.Message</p>
}
finally
{
<p>The finally statement.</p>
}
@lock
Razor tiene la capacidad de proteger las secciones más importantes con instrucciones de bloqueo:
@lock (SomeLock)
{
// Do critical section work
}
Comentarios
Razor admite comentarios tanto de C# como HTML:
@{
/* C# comment */
// Another C# comment
}
<!-- HTML comment -->
El servidor quitará los comentarios de Razor antes de mostrar la página web. Razor usa @* *@ para delimitar
comentarios. El siguiente código está comentado, de modo que el servidor no representa ningún marcado:
@*
@{
/* C# comment */
// Another C# comment
}
<!-- HTML comment -->
*@
Directivas
Las directivas de Razor se representan en las expresiones implícitas con palabras clave reservadas seguidas del
símbolo @ . Normalmente, una directiva cambia la forma en que una vista se analiza, o bien habilita una
funcionalidad diferente.
Conocer el modo en que Razor genera el código de una vista hace que sea más fácil comprender cómo
funcionan las directivas.
@{
var quote = "Getting old ain't for wimps! - Anonymous";
}
Más adelante en este artículo, en la sección Visualización de la clase C# de Razor generada por una vista, se
explica cómo ver esta clase generada.
@using
La directiva @using agrega la directiva using de C# a la vista generada:
@using System.IO
@{
var dir = Directory.GetCurrentDirectory();
}
<p>@dir</p>
@model
La directiva @model especifica el tipo del modelo que se pasa a una vista:
@model TypeNameOfModel
En una aplicación ASP.NET Core MVC creada con cuentas de usuario individuales, la vista
Views/Account/Login.cshtml contiene la siguiente declaración de modelo:
@model LoginViewModel
Razor expone una propiedad Model para tener acceso al modelo que se ha pasado a la vista:
La directiva @model especifica el tipo de esta propiedad. La directiva especifica el elemento T en RazorPage<T>
de la clase generada de la que se deriva la vista. Si la directiva @model no se especifica, la propiedad Model es de
tipo dynamic . El valor del modelo se pasa del controlador a la vista. Para más información, vea Modelos
fuertemente tipados y la palabra clave @model.
@inherits
La directiva @inherits proporciona control total sobre la clase que la vista hereda:
@inherits TypeNameOfClassToInheritFrom
using Microsoft.AspNetCore.Mvc.Razor;
@inherits CustomRazorPage<TModel>
<div>Custom text: Gardyloo! - A Scottish warning yelled from a window before dumping a slop bucket on the
street below.</div>
@model y @inherits se pueden usar en la misma vista. @inherits puede estar en un archivo
_ViewImports.cshtml que la vista importa:
@inherits CustomRazorPage<TModel>
@inherits CustomRazorPage<TModel>
@inject
La directiva @inject permite a la página de Razor insertar un servicio del contenedor de servicios en una vista.
Para más información, vea Dependency injection into views (Inserción de dependencias en vistas).
@functions
La directiva @functions permite que una página de Razor agregue un bloque de código de C# a una vista:
@functions { // C# Code }
Por ejemplo:
@functions {
public string GetHello()
{
return "Hello";
}
}
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Razor;
@section
La directiva @section se usa junto con el diseño para permitir que las vistas representen el contenido en
diferentes partes de la página HTML. Para más información, vea Sections (Secciones).
DIRECTIVA FUNCIÓN
// Look at generatedCode
return csharpDocument;
}
}
La coincidencia de mayúsculas y minúsculas garantiza que las implementaciones van a encontrar sus vistas,
independientemente de cuál sea el sistema de archivos subyacente.
Compilación de archivos de Razor en ASP.NET Core
25/06/2018 • 4 minutes to read • Edit Online
Consideraciones de precompilación
Los siguientes son los efectos secundarios de la precompilación de los archivos de Razor:
Un paquete publicado más pequeño.
Un menor tiempo de inicio.
Los archivos de Razor no se pueden editar; el contenido asociado no está presente en el paquete publicado.
IMPORTANT
El SDK de Razor solo es efectivo cuando no hay propiedades específicas de la precompilación establecidas en el archivo de
proyecto. Por ejemplo, establecer la propiedad MvcRazorCompileOnPublish del archivo .csproj en true deshabilita el SDK
de Razor.
Recursos adicionales
Vistas de ASP.NET Core MVC
Introduction to Razor Pages in ASP.NET Core
Vistas de ASP.NET Core MVC
Introduction to Razor Pages in ASP.NET Core
Vistas de ASP.NET Core MVC
ASP.NET Core Razor SDK
Diseño en ASP.NET Core
25/06/2018 • 10 minutes to read • Edit Online
Qué es un diseño
La mayoría de las aplicaciones web tienen un diseño común que ofrece al usuario una experiencia
coherente mientras navegan por sus páginas. El diseño suele incluir elementos comunes en la interfaz de
usuario, como el encabezado, los elementos de navegación o de menú y el pie de página de la aplicación.
Las estructuras HTML comunes, como los scripts y las hojas de estilos también se usan con frecuencia en
muchas páginas dentro de una aplicación. Todos estos elementos compartidos se pueden definir en un
archivo de diseño, al que se puede hacer referencia por cualquier vista que se use en la aplicación. Los
diseños reducen el código duplicado en las vistas y ayudan a seguir el principio Una vez y solo una
(DRY ).
Por convención, el diseño predeterminado para una aplicación ASP.NET se denomina _Layout.cshtml . La
plantilla de proyecto de Visual Studio ASP.NET Core MVC incluye este archivo de diseño en la carpeta
Views/Shared :
Este diseño define una plantilla de nivel superior para las vistas en la aplicación. Las aplicaciones no
necesitan un diseño y las aplicaciones pueden definir más de un diseño con distintas vistas que
especifiquen diseños diferentes.
Un ejemplo _Layout.cshtml :
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - WebApplication1</title>
<environment names="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
</environment>
<environment names="Staging,Production">
<link rel="stylesheet"
href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.6/css/bootstrap.min.css"
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-
test-value="absolute" />
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
</environment>
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-
target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a asp-area="" asp-controller="Home" asp-action="Index" class="navbar-
brand">WebApplication1</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li>
<li><a asp-area="" asp-controller="Home" asp-action="About">About</a></li>
<li><a asp-area="" asp-controller="Home" asp-action="Contact">Contact</a></li>
</ul>
@await Html.PartialAsync("_LoginPartial")
</div>
</div>
</div>
<div class="container body-content">
@RenderBody()
<hr />
<footer>
<p>© 2016 - WebApplication1</p>
</footer>
</footer>
</div>
<environment names="Development">
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
</environment>
<environment names="Staging,Production">
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js"
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery">
</script>
<script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.6/bootstrap.min.js"
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal">
</script>
<script src="~/js/site.min.js" asp-append-version="true"></script>
</environment>
Especificar un diseño
Las vistas de Razor tienen una propiedad Layout . Las vistas individuales especifican un diseño al
configurar esta propiedad:
@{
Layout = "_Layout";
}
El diseño especificado puede usar una ruta de acceso completa (ejemplo: /Views/Shared/_Layout.cshtml )
o un nombre parcial (ejemplo: _Layout ). Cuando se proporciona un nombre parcial, el motor de vista de
Razor buscará el archivo de diseño mediante su proceso de detección estándar. Primero se busca en la
carpeta asociada al controlador y después en la carpeta Shared . Este proceso de detección es idéntico al
usado para detectar vistas parciales.
De forma predeterminada, todos los diseños deben llamar a RenderBody . Cada vez que se realiza la
llamada a RenderBody , se representa el contenido de la vista.
Secciones
Opcionalmente, un diseño puede hacer referencia a una o varias secciones mediante una llamada a
RenderSection . Las secciones permiten organizar dónde se deben colocar determinados elementos de la
página. Cada llamada a RenderSection puede especificar si esa sección es obligatoria u opcional. Si no se
encuentra una sección obligatoria, se producirá una excepción. Las vistas individuales especifican el
contenido que se va a representar dentro de una sección con la sintaxis @section de Razor. Si una vista
define una sección, se debe representar (o se producirá un error).
Ejemplo de definición de @section en una vista:
@section Scripts {
<script type="text/javascript" src="/scripts/main.js"></script>
}
En el código anterior, se agregan scripts de validación a la sección scripts en una vista que incluye un
formulario. Es posible que otras vistas de la misma aplicación no necesiten scripts adicionales, por lo que
no sería necesario definir una sección de scripts.
Las secciones definidas en una vista solo están disponibles en su página de diseño inmediato. No se
puede hacer referencia a ellas desde líneas de código parcialmente ejecutadas, componentes de vista u
otros elementos del sistema de vistas.
Omitir secciones
De forma predeterminada, el cuerpo y todas las secciones de una página de contenido deben
representarse mediante la página de diseño. Para cumplir con esto, el motor de vistas de Razor
comprueba si el cuerpo y cada sección se han representado.
Para indicar al motor de vistas que pase por alto el cuerpo o las secciones, llame a los métodos
IgnoreBody y IgnoreSection .
El cuerpo y todas las secciones de una página de Razor deben representarse o pasarse por alto.
@removeTagHelper
@tagHelperPrefix
@using
@model
@inherits
@inject
El archivo no es compatible con otras características de Razor, como las funciones y las definiciones de
sección.
Archivo _ViewImports.cshtml de ejemplo:
@using WebApplication1
@using WebApplication1.Models
@using WebApplication1.Models.AccountViewModels
@using WebApplication1.Models.ManageViewModels
@using Microsoft.AspNetCore.Identity
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
El archivo _ViewImports.cshtml para una aplicación ASP.NET Core MVC normalmente se coloca en la
carpeta Views . Un archivo _ViewImports.cshtml puede colocarse dentro de cualquier carpeta, en cuyo
caso solo se aplicará a vistas dentro de esa carpeta y sus subcarpetas. Los archivos _ViewImports se
procesan a partir del nivel de raíz y después para cada carpeta que lleva hasta la ubicación de la propia
vista, por lo que la configuración especificada en el nivel de raíz es posible que se invalide en el nivel de
carpeta.
Por ejemplo, si un archivo _ViewImports.cshtml de nivel de raíz especifica @model y @addTagHelper , y
otro archivo _ViewImports.cshtml en la carpeta asociada al controlador de la vista especifica un @model
diferente y agrega otro @addTagHelper , la vista tendrá acceso a las aplicaciones auxiliares de etiquetas y
usará el último @model .
Si se ejecutan varios archivos _ViewImports.cshtml para una vista, el comportamiento combinado de las
directivas incluidas en los archivos ViewImports.cshtml será el siguiente:
@addTagHelper , @removeTagHelper : todos se ejecutan en orden
@tagHelperPrefix : el más cercano a la vista invalida los demás
@model : el más cercano a la vista invalida los demás
@inherits : el más cercano a la vista invalida los demás
@using : todos se incluyen y se omiten los duplicados
: para cada propiedad, la más cercana a la vista invalida cualquier otra con el mismo
@inject
nombre de propiedad
@{
Layout = "_Layout";
}
El archivo anterior especifica que todas las vistas usarán el diseño _Layout.cshtml .
NOTE
Ni _ViewStart.cshtml ni _ViewImports.cshtml se colocan normalmente en la carpeta /Views/Shared . Las
versiones de nivel de aplicación de estos archivos deben colocarse directamente en la carpeta /Views .
Aplicaciones auxiliares de etiquetas en
ASP.NET Core
14/05/2018 • 25 minutes to read • Edit Online
<label asp-for="Movie.Title"></label>
<label for="Movie_Title">Title</label>
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, AuthoringTagHelpers
La directiva @addTagHelper hace que las aplicaciones auxiliares de etiquetas estén
disponibles en la vista. En este caso, el archivo de vista es Views/_ViewImports.cshtml, el
cual heredan de forma predeterminada todos los archivos de vista de la carpeta Views y los
subdirectorios, lo que hace que las aplicaciones auxiliares de etiquetas estén disponibles. El
código anterior usa la sintaxis de comodines ("*") para especificar que todas las aplicaciones
auxiliares de etiquetas del ensamblado especificado
(Microsoft.AspNetCore.Mvc.TagHelpers) estarán disponibles para todos los archivos de
vista del directorio o subdirectorio Views. El primer parámetro después de @addTagHelper
especifica las aplicaciones auxiliares de etiquetas que se van a cargar (usamos "*" para todas
las aplicaciones auxiliares de etiquetas), y el segundo parámetro
("Microsoft.AspNetCore.Mvc.TagHelpers") especifica el ensamblado que contiene las
aplicaciones auxiliares de etiquetas. Microsoft.AspNetCore.Mvc.TagHelpers es el
ensamblado para las aplicaciones auxiliares de etiquetas integradas de ASP.NET Core.
Para exponer todas las aplicaciones auxiliares de etiquetas de este proyecto (que crea un
ensamblado denominado AuthoringTagHelpers), use lo siguiente:
@using AuthoringTagHelpers
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, AuthoringTagHelpers
@using AuthoringTagHelpers
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper AuthoringTagHelpers.TagHelpers.EmailTagHelper, AuthoringTagHelpers
Para agregar una aplicación auxiliar de etiquetas a una vista con un FQN, agregue primero
el FQN ( AuthoringTagHelpers.TagHelpers.EmailTagHelper ) y, después, el nombre del
ensamblado (AuthoringTagHelpers). La mayoría de los desarrolladores prefiere usar la
sintaxis de comodines "*". La sintaxis de comodines permite insertar el carácter comodín "*"
como sufijo en un FQN. Por ejemplo, cualquiera de las siguientes directivas incorporará
EmailTagHelper :
@tagHelperPrefix th:
No solo obtendrá ayuda HTML, sino el icono (el "@" symbol with "<>" situado debajo).
Esto identifica el elemento como el destino de las aplicaciones auxiliares de etiquetas. Los
elementos HTML puros (como fieldset ) muestran el icono "<>".
Una etiqueta HTML pura <label> muestra la etiqueta HTML (con el tema de color de
Visual Studio predeterminado) en una fuente marrón, con los atributos en rojo y los valores
de atributo en azul.
Puede usar el acceso directo CompleteWord de Visual Studio (el valor predeterminado es
Ctrl+barra espaciadora) entre comillas dobles (""). Ahora se encuentra en C#, como si
estuviera en una clase de C#. IntelliSense muestra todos los métodos y propiedades en el
modelo de páginas. Los métodos y las propiedades están disponibles porque el tipo de
propiedad es ModelExpression . En la imagen siguiente se edita la vista Register , por lo que
RegisterViewModel está disponible.
El símbolo de arroba ( @ ) le indica a Razor que este es el inicio del código. Los dos
parámetros siguientes ("FirstName" y "First Name:") son cadenas, por lo que IntelliSense no
sirve de ayuda. El último argumento:
new {@class="caption"}
Es un objeto anónimo que se usa para representar atributos. Dado que class es una palabra
reservada en C#, use el símbolo @ para forzar a C# a interpretar "@class=" como un
símbolo (nombre de propiedad). Para un diseñador de front-end (es decir, alguien
familiarizado con HTML, CSS, JavaScript y otras tecnologías de cliente, pero desconocedor
de C# y Razor), la mayor parte de la línea le resulta ajena. Es necesario crear toda la línea
sin ayuda de IntelliSense.
Si usa LabelTagHelper , se puede escribir el mismo marcado de la manera siguiente:
IntelliSense le ayuda a escribir toda la línea. LabelTagHelper también tiene como valor
predeterminado el establecimiento del contenido del valor de atributo asp-for
("FirstName") en "First Name"; es decir, convierte las propiedades con grafía Camel en una
oración formada por el nombre de la propiedad con un espacio donde aparece cada nueva
letra mayúscula. En el marcado siguiente:
Se genera:
Se genera:
@Html.AntiForgeryToken()
Se muestra con un fondo gris. La mayor parte del marcado en la vista de registro es de C#.
Compárelo con el método equivalente con aplicaciones auxiliares de etiquetas:
El marcado es mucho más ordenado y más fácil de leer, modificar y mantener que en el
método de aplicaciones auxiliares HTML. El código de C# se reduce a la cantidad mínima
que el servidor necesita conocer. En el editor de Visual Studio se muestra el marcado de
destino de una aplicación auxiliar de etiquetas en una fuente distinta.
Observe el grupo Email:
<div class="form-group">
<label asp-for="Email" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
</div>
Cada uno de los atributos "asp-" tiene un valor de "Email", pero "Email" no es una cadena.
En este contexto, "Email" es la propiedad de expresión del modelo de C# para
RegisterViewModel .
Recursos adicionales
Creación de aplicaciones auxiliares de etiquetas
Trabajar con formularios
TagHelperSamples en GitHub contiene ejemplos de aplicaciones auxiliares de etiquetas
para trabajar con Bootstrap.
Crear aplicaciones auxiliares de etiquetas en
ASP.NET Core
25/06/2018 • 28 minutes to read • Edit Online
<email>Support</email>
El servidor usará nuestra aplicación auxiliar de etiquetas de correo electrónico para convertir ese marcado en lo
siguiente:
<a href="mailto:[email protected]">[email protected]</a>
Es decir, una etiqueta delimitadora lo convierte en un vínculo de correo electrónico. Tal vez le interese si está
escribiendo un motor de blogs y necesita que envíe correos electrónicos a contactos de marketing, soporte
técnico y de otro tipo, todos ellos en el mismo dominio.
1. Agregue la siguiente clase EmailTagHelper a la carpeta TagHelpers.
using Microsoft.AspNetCore.Razor.TagHelpers;
using System.Threading.Tasks;
namespace AuthoringTagHelpers.TagHelpers
{
public class EmailTagHelper : TagHelper
{
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "a"; // Replaces <email> with <a> tag
}
}
}
Notas:
Las aplicaciones auxiliares de etiquetas usan una convención de nomenclatura que tiene como
destino los elementos de la clase raíz (menos la parte TagHelper del nombre de clase). En este
ejemplo, el nombre raíz de EmailTagHelper es email, por lo que el destino será la etiqueta
<email> . Esta convención de nomenclatura debería funcionar para la mayoría de las aplicaciones
auxiliares de etiquetas. Más adelante veremos cómo invalidarla.
La clase EmailTagHelper se deriva de TagHelper . La clase TagHelper proporciona métodos y
propiedades para escribir aplicaciones auxiliares de etiquetas.
El método Process invalidado controla lo que hace la aplicación auxiliar de etiquetas cuando se
ejecuta. La clase TagHelper también proporciona una versión asincrónica ( ProcessAsync ) con los
mismos parámetros.
El parámetro de contexto para Process (y ProcessAsync ) contiene información relacionada con la
ejecución de la etiqueta HTML actual.
El parámetro de salida para Process (y ProcessAsync ) contiene un elemento HTML con estado
que representa el origen original usado para generar una etiqueta y contenido HTML.
El nombre de nuestra clase tiene un sufijo TagHelper, que no es necesario, pero es una
convención recomendada. Podría declarar la clase de la manera siguiente:
2. Para hacer que la clase EmailTagHelper esté disponible para todas nuestras vistas de Razor, agregue la
directiva addTagHelper al archivo Views/_ViewImports.cshtml: [!code-html]
El código anterior usa la sintaxis de comodines para especificar que todas las aplicaciones auxiliares de
etiquetas del ensamblado estarán disponibles. La primera cadena después de @addTagHelper especifica la
aplicación auxiliar de etiquetas que se va a cargar (use "*" para todas las aplicaciones auxiliares de
etiquetas), mientras que la segunda cadena "AuthoringTagHelpers" especifica el ensamblado en el que se
encuentra la aplicación auxiliar de etiquetas. Además, tenga en cuenta que la segunda línea incorpora las
aplicaciones auxiliares de etiquetas de ASP.NET Core MVC mediante la sintaxis de comodines (esas
aplicaciones auxiliares se tratan en el tema Introducción a las aplicaciones auxiliares de etiquetas). Es la
directiva @addTagHelper la que hace que la aplicación auxiliar de etiquetas esté disponible para la vista de
Razor. Como alternativa, puede proporcionar el nombre completo (FQN ) de una aplicación auxiliar de
etiquetas como se muestra a continuación:
@using AuthoringTagHelpers
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper AuthoringTagHelpers.TagHelpers.EmailTagHelper, AuthoringTagHelpers
Para agregar una aplicación auxiliar de etiquetas a una vista con un FQN, agregue primero el FQN (
AuthoringTagHelpers.TagHelpers.EmailTagHelper ) y, después, el nombre del ensamblado ( AuthoringTagHelpers).
La mayoría de los desarrolladores prefiere usar la sintaxis de comodines. En Introducción a las aplicaciones
auxiliares de etiquetas se describe en detalle la adición y eliminación de aplicaciones auxiliares de etiquetas, la
jerarquía y la sintaxis de comodines.
3. Actualice el marcado del archivo Views/Home/Contact.cshtml con estos cambios:
@{
ViewData["Title"] = "Contact";
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>
<address>
One Microsoft Way<br />
Redmond, WA 98052<br />
<abbr title="Phone">P:</abbr>
425.555.0100
</address>
<address>
<strong>Support:</strong><email>Support</email><br />
<strong>Marketing:</strong><email>Marketing</email>
</address>
4. Ejecute la aplicación y use su explorador favorito para ver el código fuente HTML, a fin de comprobar que
las etiquetas de correo electrónico se han reemplazado por un marcado delimitador (por ejemplo,
<a>Support</a> ). Support y Marketing se representan como vínculos, pero no tienen un atributo href
que los haga funcionales. Esto lo corregiremos en la sección siguiente.
SetAttribute y SetContent
En esta sección, actualizaremos EmailTagHelper para que cree una etiqueta delimitadora válida para correo
electrónico. Lo actualizaremos para que tome información de una vista de Razor (en forma de atributo mail-to )
y la use al generar el delimitador.
Actualice la clase EmailTagHelper con lo siguiente:
public class EmailTagHelper : TagHelper
{
private const string EmailDomain = "contoso.com";
Notas:
Los nombres de clase y propiedad con grafía Pascal para las aplicaciones auxiliares de etiquetas se
convierten a su grafía kebab en minúsculas. Por tanto, para usar el atributo MailTo , usará su equivalente
<email mail-to="value"/> .
La última línea establece el contenido completado para nuestra aplicación auxiliar de etiquetas
mínimamente funcional.
La línea resaltada muestra la sintaxis para agregar atributos:
Este enfoque funciona para el atributo "href" siempre y cuando no exista actualmente en la colección de
atributos. También puede usar el método output.Attributes.Add para agregar un atributo de aplicación auxiliar
de etiquetas al final de la colección de atributos de etiqueta.
1. Actualice el marcado del archivo Views/Home/Contact.cshtml con estos cambios: [!code-html]
2. Ejecute la aplicación y compruebe que genera los vínculos correctos.
NOTE
Si escribe la etiqueta de correo electrónico como de autocierre ( <email mail-to="Rick" /> ), la salida final
también será de autocierre. Para habilitar la capacidad de escribir la etiqueta únicamente con una etiqueta de
apertura ( <email mail-to="Rick"> ) debe decorar la clase con lo siguiente:
Con una aplicación auxiliar de etiquetas de correo electrónico de autocierre, el resultado sería
<a href="mailto:[email protected]" /> . Las etiquetas delimitadoras de autocierre no son HTML válido,
por lo que no le interesa crear una, pero tal vez le convenga crear una aplicación auxiliar de etiquetas de
autocierre. Las aplicaciones auxiliares de etiquetas establecen el tipo de la propiedad TagMode después de
leer una etiqueta.
ProcessAsync
En esta sección, escribiremos una aplicación auxiliar de correo electrónico asincrónica.
1. Reemplace la clase EmailTagHelper por el siguiente código:
Notas:
Esta versión usa el método ProcessAsync asincrónico. El método GetChildContentAsync
asincrónico devuelve un valor Task que contiene TagHelperContent .
Use el parámetro output para obtener el contenido del elemento HTML.
2. Realice el cambio siguiente en el archivo Views/Home/Contact.cshtml para que la aplicación auxiliar de
etiquetas pueda obtener el correo electrónico de destino.
@{
ViewData["Title"] = "Contact";
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>
<address>
One Microsoft Way<br />
Redmond, WA 98052<br />
<abbr title="Phone">P:</abbr>
425.555.0100
</address>
<address>
<strong>Support:</strong><email>Support</email><br />
<strong>Marketing:</strong><email>Marketing</email>
</address>
namespace AuthoringTagHelpers.TagHelpers
{
[HtmlTargetElement(Attributes = "bold")]
public class BoldTagHelper : TagHelper
{
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.Attributes.RemoveAll("bold");
output.PreContent.SetHtmlContent("<strong>");
output.PostContent.SetHtmlContent("</strong>");
}
}
}
Notas:
El atributo [HtmlTargetElement] pasa un parámetro de atributo que especifica que todos los
elementos HTML que contengan un atributo HTML denominado "bold" coincidirán, y se ejecutará
el método de invalidación Process de la clase. En nuestro ejemplo, el método Process quita el
atributo "bold" y rodea el marcado contenedor con <strong></strong> .
Dado que no le interesa reemplazar el contenido existente de la etiqueta, debe escribir la etiqueta
de apertura <strong> con el método PreContent.SetHtmlContent y la etiqueta de cierre </strong>
con el método PostContent.SetHtmlContent .
2. Modifique la vista About.cshtml para que contenga un valor de atributo bold . A continuación se muestra
el código completado.
@{
ViewData["Title"] = "About";
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>
3. Ejecute la aplicación. Puede usar el explorador que prefiera para inspeccionar el origen y comprobar el
marcado.
El atributo [HtmlTargetElement] anterior solo tiene como destino el marcado HTML que proporciona el
nombre de atributo "bold". La aplicación auxiliar de etiquetas no ha modificado el elemento <bold> .
4. Convierta en comentario la línea de atributo [HtmlTargetElement] y de forma predeterminada tendrá
como destino las etiquetas <bold> , es decir, el marcado HTML con formato <bold> . Recuerde que la
convención de nomenclatura predeterminada hará coincidir el nombre de clase BoldTagHelper con las
etiquetas <bold> .
5. Ejecute la aplicación y compruebe que la aplicación auxiliar de etiquetas procesa la etiqueta <bold> .
El proceso de decorar una clase con varios atributos [HtmlTargetElement] tiene como resultado una operación
OR lógica de los destinos. Por ejemplo, si se usa el código siguiente, una etiqueta bold o un atributo bold
coincidirán.
[HtmlTargetElement("bold")]
[HtmlTargetElement(Attributes = "bold")]
public class BoldTagHelper : TagHelper
{
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.Attributes.RemoveAll("bold");
output.PreContent.SetHtmlContent("<strong>");
output.PostContent.SetHtmlContent("</strong>");
}
}
Cuando se agregan varios atributos a la misma instrucción, el tiempo de ejecución los trata como una operación
AND lógica. Por ejemplo, en el código siguiente, un elemento HTML debe denominarse "bold" con un atributo
denominado "bold" ( <bold bold /> ) para que coincida.
También puede usar [HtmlTargetElement] para cambiar el nombre del elemento de destino. Por ejemplo, si
quiere que BoldTagHelper tenga como destino etiquetas <MyBold> , use el atributo siguiente:
[HtmlTargetElement("MyBold")]
using System;
namespace AuthoringTagHelpers.Models
{
public class WebsiteContext
{
public Version Version { get; set; }
public int CopyrightYear { get; set; }
public bool Approved { get; set; }
public int TagsToShow { get; set; }
}
}
namespace AuthoringTagHelpers.TagHelpers
{
public class WebsiteInformationTagHelper : TagHelper
{
public WebsiteContext Info { get; set; }
Notas:
Como se ha indicado anteriormente, las aplicaciones auxiliares de etiquetas convierten las
propiedades y nombres de clase de C# con grafía Pascal para aplicaciones auxiliares de etiquetas
en grafía kebab en minúsculas. Por tanto, para usar WebsiteInformationTagHelper en Razor, deberá
escribir <website-information /> .
No está identificando de manera explícita el elemento de destino con el atributo
[HtmlTargetElement] , por lo que el destino será el valor predeterminado de website-information .
Si ha aplicado el atributo siguiente (tenga en cuenta que no tiene grafía kebab, pero coincide con el
nombre de clase):
[HtmlTargetElement("WebsiteInformation")]
La etiqueta con grafía kebab en minúsculas <website-information /> no coincidiría. Si quiere usar el
atributo [HtmlTargetElement] , debe usar la grafía kebab como se muestra a continuación:
[HtmlTargetElement("Website-Information")]
Los elementos que son de autocierre no tienen contenido. En este ejemplo, el marcado de Razor
usará una etiqueta de autocierre, pero la aplicación auxiliar de etiquetas creará un elemento
section (que no es de autocierre, y el contenido se escribirá dentro del elemento section ). Por
tanto, debe establecer TagMode en StartTagAndEndTag para escribir la salida. Como alternativa,
puede convertir en comentario la línea donde se establece TagMode y escribir marcado con una
etiqueta de cierre. (Más adelante en este tutorial se proporciona marcado de ejemplo).
El signo de dólar $ de la línea siguiente usa una cadena interpolada:
$@"<ul><li><strong>Version:</strong> {Info.Version}</li>
NOTE
En el marcado de Razor que se muestra a continuación:
Razor sabe que el atributo info es una clase, no una cadena, y usted quiere escribir código de C#. Todos los
atributos de aplicaciones auxiliares de etiquetas que no sean una cadena deben escribirse sin el carácter @ .
5. Ejecute la aplicación y vaya a la vista About para ver la información del sitio web.
NOTE
Puede usar el marcado siguiente con una etiqueta de cierre y quitar la línea con TagMode.StartTagAndEndTag de
la aplicación auxiliar de etiquetas:
namespace AuthoringTagHelpers.TagHelpers
{
[HtmlTargetElement(Attributes = nameof(Condition))]
public class ConditionTagHelper : TagHelper
{
public bool Condition { get; set; }
@using AuthoringTagHelpers.Models
@model WebsiteContext
@{
ViewData["Title"] = "Home Page";
}
<div>
<h3>Information about our website (outdated):</h3>
<website-information info=@Model />
<div condition="@Model.Approved">
<p>
This website has <strong surround="em"> @Model.Approved </strong> been approved yet.
Visit www.contoso.com for more information.
</p>
</div>
</div>
4. Ejecute la aplicación y vaya a la página principal. El marcado del elemento condicional div no se
representará. Anexe la cadena de consulta ?approved=true a la dirección URL (por ejemplo,
http://localhost:1235/Home/Index?approved=true ). approved se establece en true y se muestra el marcado
condicional.
NOTE
Use el operador nameof para especificar el atributo de destino en lugar de especificar una cadena, como hizo con la
aplicación auxiliar de etiquetas bold:
[HtmlTargetElement(Attributes = nameof(Condition))]
// [HtmlTargetElement(Attributes = "condition")]
public class ConditionTagHelper : TagHelper
{
public bool Condition { get; set; }
El operador nameof protegerá el código si en algún momento debe refactorizarse (tal vez interese cambiar el nombre a
RedCondition ).
[HtmlTargetElement("p")]
public class AutoLinkerHttpTagHelper : TagHelper
{
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var childContent = await output.GetChildContentAsync();
// Find Urls in the content and replace them with their anchor tag equivalent.
output.Content.SetHtmlContent(Regex.Replace(
childContent.GetContent(),
@"\b(?:https?://)(\S+)\b",
"<a target=\"_blank\" href=\"$0\">$0</a>")); // http link version}
}
}
NOTE
La clase AutoLinkerHttpTagHelper tiene como destino elementos p y usa Regex para crear el delimitador.
<address>
One Microsoft Way<br />
Redmond, WA 98052<br />
<abbr title="Phone">P:</abbr>
425.555.0100
</address>
<address>
<strong>Support:</strong><email>Support</email><br />
<strong>Marketing:</strong><email>Marketing</email>
</address>
[HtmlTargetElement("p")]
public class AutoLinkerHttpTagHelper : TagHelper
{
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var childContent = await output.GetChildContentAsync();
// Find Urls in the content and replace them with their anchor tag equivalent.
output.Content.SetHtmlContent(Regex.Replace(
childContent.GetContent(),
@"\b(?:https?://)(\S+)\b",
"<a target=\"_blank\" href=\"$0\">$0</a>")); // http link version}
}
}
[HtmlTargetElement("p")]
public class AutoLinkerWwwTagHelper : TagHelper
{
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var childContent = await output.GetChildContentAsync();
// Find Urls in the content and replace them with their anchor tag equivalent.
output.Content.SetHtmlContent(Regex.Replace(
childContent.GetContent(),
@"\b(www\.)(\S+)\b",
"<a target=\"_blank\" href=\"http://$0\">$0</a>")); // www version
}
}
}
5. Ejecute la aplicación. Observe que el texto www se representa como un vínculo, a diferencia del texto
HTTP. Si coloca un punto de interrupción en ambas clases, verá que la clase de la aplicación auxiliar de
etiquetas HTTP se ejecuta primero. El problema es que la salida de la aplicación auxiliar de etiquetas se
almacena en caché y, cuando se ejecuta la aplicación auxiliar de etiquetas WWW, sobrescribe la salida
almacenada en caché desde la aplicación auxiliar de etiquetas HTTP. Más adelante en el tutorial veremos
cómo se controla el orden en el que se ejecutan las aplicaciones auxiliares de etiquetas. Corregiremos el
código con lo siguiente:
// Find Urls in the content and replace them with their anchor tag equivalent.
output.Content.SetHtmlContent(Regex.Replace(
childContent,
@"\b(?:https?://)(\S+)\b",
"<a target=\"_blank\" href=\"$0\">$0</a>")); // http link version}
}
}
[HtmlTargetElement("p")]
public class AutoLinkerWwwTagHelper : TagHelper
{
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var childContent = output.Content.IsModified ? output.Content.GetContent() :
(await output.GetChildContentAsync()).GetContent();
// Find Urls in the content and replace them with their anchor tag equivalent.
output.Content.SetHtmlContent(Regex.Replace(
childContent,
@"\b(www\.)(\S+)\b",
"<a target=\"_blank\" href=\"http://$0\">$0</a>")); // www version
}
}
NOTE
La primera vez que editó las aplicaciones auxiliares de etiquetas de vinculación automática, obtuvo el contenido del
destino con el código siguiente:
El código anterior comprueba si se ha modificado el contenido y, en caso afirmativo, obtiene el contenido del búfer
de salida.
6. Ejecute la aplicación y compruebe que los dos vínculos funcionan según lo previsto. Aunque podría
parecer que nuestra aplicación auxiliar de etiquetas de vinculación automática es correcta y está
completa, tiene un pequeño problema. Si la aplicación auxiliar de etiquetas WWW se ejecuta en primer
lugar, los vínculos www no serán correctos. Actualice el código mediante la adición de la sobrecarga
Order para controlar el orden en el que se ejecuta la etiqueta. La propiedad Order determina el orden
de ejecución en relación con las demás aplicaciones auxiliares de etiquetas que tienen como destino el
mismo elemento. El valor de orden predeterminado es cero, y se ejecutan en primer lugar las instancias
con los valores más bajos.
public class AutoLinkerHttpTagHelper : TagHelper
{
// This filter must run before the AutoLinkerWwwTagHelper as it searches and replaces http and
// the AutoLinkerWwwTagHelper adds http to the markup.
public override int Order
{
get { return int.MinValue; }
}
El código anterior garantizará que la aplicación auxiliar de etiquetas HTTP se ejecute antes que la
aplicación auxiliar de etiquetas WWW. Cambie Order a MaxValue y compruebe que el marcado
generado para la etiqueta WWW es incorrecto.
// Find Urls in the content and replace them with their anchor tag equivalent.
output.Content.SetHtmlContent(Regex.Replace(
childContent,
@"\b(?:https?://)(\S+)\b",
"<a target=\"_blank\" href=\"$0\">$0</a>")); // http link version}
}
}
El tiempo de ejecución MVC genera el valor de atributo action de los atributos de aplicación auxiliar de etiquetas
Form asp-controller y asp-action . La aplicación auxiliar de etiquetas Form genera también un token de
comprobación de solicitudes oculto que impide que se falsifiquen solicitudes entre sitios (cuando se usa con el
atributo [ValidateAntiForgeryToken] en el método de acción HTTP Post). Proteger un elemento HTML Form puro
de la falsificación de solicitudes entre sitios no es tarea fácil, y la aplicación auxiliar de etiquetas Form presta este
servicio.
Uso de una ruta con nombre
El atributo de aplicación auxiliar de etiquetas asp-route puede generar también el marcado del atributo HTML
action . Una aplicación con una ruta denominada register podría usar el siguiente marcado para la página de
registro:
Muchas de las vistas de la carpeta Views/Account (que se genera cuando se crea una aplicación web con Cuentas
de usuario individuales) contienen el atributo asp-route-returnurl:
NOTE
Con las plantillas integradas, returnUrl se rellena automáticamente solo cuando alguien intenta obtener acceso a un
recurso autorizado, pero no se ha autenticado o no tiene autorización. Si se intenta realizar un acceso no autorizado, el
middleware de seguridad redirige a la página de inicio de sesión con returnUrl configurado.
Type expected
'RegisterViewModel' does not contain a definition for 'Email' and no
extension method 'Email' accepting a first argument of type 'RegisterViewModel'
could be found (are you missing a using directive or an assembly reference?)
La aplicación auxiliar de etiquetas Input establece el atributo HTML type en función del tipo .NET. En la siguiente
tabla se enumeran algunos tipos .NET habituales y el tipo HTML generado correspondiente (no incluimos aquí
todos los tipos .NET).
Bool type=”checkbox”
String type=”text”
DateTime type=”datetime”
Byte type=”number”
En la siguiente tabla se muestran algunos atributos de anotación de datos comunes que la aplicación auxiliar de
etiquetas Input asignará a tipos de entrada concretos (no incluimos aquí todos los atributo de validación):
[EmailAddress] type=”email”
[Url] type=”url”
[HiddenInput] type=”hidden”
[Phone] type=”tel”
[DataType(DataType.Password)] type=”password”
[DataType(DataType.Date)] type=”date”
[DataType(DataType.Time)] type=”time”
Ejemplo:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
}
@model RegisterViewModel
Las anotaciones de datos que se aplican a las propiedades Email y Password generan metadatos en el modelo. La
aplicación auxiliar de etiquetas Input usa esos metadatos del modelo y genera atributos HTML5 data-val-* . Vea
Model Validation (Validación del modelo). Estos atributos describen los validadores que se van a adjuntar a los
campos de entrada, lo que proporciona HTML5 discreto y validación de jQuery. Los atributos discretos tienen el
formato data-val-rule="Error Message" , donde "rule" es el nombre de la regla de validación (como
data-val-required , data-val-email , data-val-maxlength , etc.). Si aparece un mensaje de error en el atributo, se
mostrará como el valor del atributo data-val-rule . También hay atributos con el formato
data-val-ruleName-argumentName="argumentValue" que aportan más información sobre la regla, por ejemplo,
data-val-maxlength-max="1024" .
Nombres de expresión
El valor del atributo asp-for es una ModelExpression y la parte de la derecha de una expresión lambda. Por tanto,
asp-for="Property1" se convierte en m => m.Property1 en el código generado, motivo por el que no es necesario
incluir el prefijo Model . Puede usar el carácter "@" para iniciar una expresión insertada y moverla antes de `m.`:
</span><span class="sxs-lookup">You can use the "@" character to start an inline expression and move before
the m. :
@{
var joe = "Joe";
}
<input asp-for="@joe" />
[DataType(DataType.Password)]
public string Password { get; set; }
@model RegisterAddressViewModel
El método de acción:
El siguiente código de Razor muestra cómo se tiene acceso a un elemento Color concreto:
@model Person
@{
var index = (int)ViewData["index"];
}
@model string
<label asp-for="@Model"></label>
<input asp-for="@Model" /> <br />
@model List<ToDoItem>
</table>
<button type="submit">Save</button>
</form>
La plantilla Views/Shared/EditorTemplates/ToDoItem.cshtml:
@model ToDoItem
<td>
<label asp-for="@Model.Name"></label>
@Html.DisplayFor(model => model.Name)
</td>
<td>
<input asp-for="@Model.IsDone" />
</td>
@*
This template replaces the following Razor which evaluates the indexer three times.
<td>
<label asp-for="@Model[i].Name"></label>
@Html.DisplayFor(model => model[i].Name)
</td>
<td>
<input asp-for="@Model[i].IsDone" />
</td>
*@
NOTE
Use siempre for (y no foreach ) para iterar por una lista. Evaluar un indizador en una expresión de LINQ puede ser
costoso, con lo cual esa posibilidad hay que reducirla al mínimo.
NOTE
El código de ejemplo comentado anterior muestra cómo reemplazaríamos la expresión lambda por el operador @ para tener
acceso a cada elemento ToDoItem de la lista.
Ejemplo:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class DescriptionViewModel
{
[MinLength(5)]
[MaxLength(1024)]
public string Description { get; set; }
}
}
@model DescriptionViewModel
Label Tag Helper proporciona las siguientes ventajas con respecto a un elemento HTML Label puro:
Obtendrá automáticamente el valor de la etiqueta descriptiva del atributo Display . El nombre para mostrar
que se busca puede cambiar con el tiempo y la combinación del atributo Display , y la aplicación auxiliar de
etiquetas Label aplicará el elemento Display en cualquier lugar donde se use.
El código fuente contiene menos marcado.
Permite establecer tipado fuerte con la propiedad de modelo.
Ejemplo:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class SimpleViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
}
}
@model SimpleViewModel
La aplicación auxiliar de etiquetas Label genera el valor de atributo for de "Email", que es el identificador asociado
al elemento <input> . Las aplicaciones auxiliares de etiqueta generan elementos id y for coherentes para que se
puedan asociar correctamente. El título de este ejemplo proviene del atributo Display . Si el modelo no contuviera
un atributo Display , el título sería el nombre de propiedad de la expresión.
Validation Message Tag Helper se usa con el atributo asp-validation-for en un elemento HTML span.
<span asp-validation-for="Email"></span>
<span class="field-validation-valid"
data-valmsg-for="Email"
data-valmsg-replace="true"></span>
Validation Message Tag Helper suele usarse por lo general después de una aplicación auxiliar de etiquetas Input
en la misma propiedad. Gracias a esto, se mostrarán todos los mensajes de error de validación cerca de la entrada
que produjo el error.
NOTE
Debe tener una vista con las referencias de script de JavaScript y de jQuery adecuadas para la validación del lado cliente. Para
más información, vea Introduction to model validation in ASP.NET Core MVC (Introducción a la validación de modelos en
ASP.NET Core MVC).
Cuando se produce un error de validación del lado servidor (por ejemplo, porque haya una validación del lado
servidor personalizada o porque la validación del lado cliente esté deshabilitada), MVC pone ese mensaje de error
como cuerpo del elemento <span> .
Validation Summary Tag Helper se usa para mostrar un resumen de los mensajes de validación. El valor de atributo
asp-validation-summary puede ser cualquiera de los siguientes:
ValidationSummary.ModelOnly Modelo
ValidationSummary.None Ninguna
Ejemplo
En el siguiente ejemplo, el modelo de datos se complementa con atributos DataAnnotation , lo que genera mensajes
de error de validación sobre el elemento <input> . Cuando se produce un error de validación, la aplicación auxiliar
de etiquetas de validación muestra el mensaje de error:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
}
@model RegisterViewModel
Ejemplo:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace FormsTagHelper.ViewModels
{
public class CountryViewModel
{
public string Country { get; set; }
El método Index inicializa CountryViewModel , establece el país seleccionado y lo pasa a la vista Index .
La vista Index :
@model CountryViewModel
NOTE
No se recomienda usar ViewBag o ViewData con la aplicación auxiliar de etiquetas Select. Un modelo de vista es más eficaz
a la hora de proporcionar metadatos MVC y suele ser menos problemático.
El valor de atributo asp-for es un caso especial y no necesita un prefijo Model , mientras que los otros atributos de
aplicación auxiliar de etiquetas sí (como asp-items ).
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}
@model CountryEnumViewModel
Puede complementar la lista de enumeradores con el atributo Display para obtener una interfaz de usuario más
completa:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}
Agrupamiento de opciones
El elemento HTML <optgroup> se genera cuando el modelo de vista contiene uno o varios objetos
SelectListGroup .
CountryViewModelGroup agrupa los elementos SelectListItem en los grupos "North America" y "Europe":
public class CountryViewModelGroup
{
public CountryViewModelGroup()
{
var NorthAmericaGroup = new SelectListGroup { Name = "North America" };
var EuropeGroup = new SelectListGroup { Name = "Europe" };
Selección múltiple
La aplicación auxiliar de etiquetas Select generará automáticamente el atributo multiple = "multiple" si la propiedad
especificada en el atributo asp-for es IEnumerable . Por ejemplo, si tenemos el siguiente modelo:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace FormsTagHelper.ViewModels
{
public class CountryViewModelIEnumerable
{
public IEnumerable<string> CountryCodes { get; set; }
Sin selección
Si ve que usa la opción "sin especificar" en varias páginas, puede crear una plantilla para no tener que repetir el
código HTML:
@model CountryViewModel
La plantilla Views/Shared/EditorTemplates/CountryViewModel.cshtml:
@model CountryViewModel
La posibilidad de agregar elementos HTML <option> no se limita exclusivamente a los casos en los que no se
seleccionada nada. Por ejemplo, el método de acción y vista siguientes generarán un código HTML similar al
código anterior:
Se seleccionará el elemento <option> correcto (que contenga el atributo selected="selected" ) en función del valor
real de Country .
Recursos adicionales
Aplicaciones auxiliares de etiquetas
HTML Form element (Elemento HTML Form)
Request Verification Token (Token de comprobación de solicitudes)
Enlace de modelos
Introduction to model validation in ASP.NET Core MVC (Introducción a la validación de modelos en ASP.NET
Core MVC )
IAttributeAdapter Interface (Interfaz IAttributeAdapter)
Fragmentos de código de este documento
Aplicaciones auxiliares de etiquetas integradas de
ASP.NET Core
21/06/2018 • 2 minutes to read • Edit Online
NOTE
Algunas aplicaciones auxiliares de etiquetas integradas no figuran en la sección, ya que se usan internamente en el motor de
vistas de Razor. Esto incluye una aplicación auxiliar de etiquetas para el carácter ~, que se expande hasta la ruta de acceso raíz
del sitio web.
Recursos adicionales
Desarrollo del lado cliente
Aplicaciones auxiliares de etiquetas
Aplicación auxiliar de etiquetas delimitadoras en
ASP.NET Core
25/06/2018 • 11 minutes to read • Edit Online
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Linq;
[Route("Speaker/{id:int}")]
public IActionResult Detail(int id) =>
View(Speakers.FirstOrDefault(a => a.SpeakerId == id));
[Route("/Speaker/Evaluations",
Name = "speakerevals")]
public IActionResult Evaluations() => View();
[Route("/Speaker/EvaluationsCurrent",
Name = "speakerevalscurrent")]
public IActionResult Evaluations(
int speakerId,
bool currentYear) => View();
asp-controller
El atributo asp-controller asigna el controlador usado para generar la dirección URL. En el marcado siguiente
se especifican todos los altavoces:
<a asp-controller="Speaker"
asp-action="Index">All Speakers</a>
asp-action
El valor del atributo asp-action representa el nombre de la acción del controlador incluido en el atributo href
generado. El siguiente marcado establece el valor del atributo href generado en la página "Speaker
Evaluations":
<a asp-controller="Speaker"
asp-action="Evaluations">Speaker Evaluations</a>
Si no hay ningún atributo asp-controller especificado, se usará el controlador predeterminado que llama a la
vista que se está ejecutando.
Si el valor del atributo asp-action es Index , no se anexa ninguna acción a la dirección URL, lo que da lugar a
la invocación de la acción Index predeterminada. La acción especificada (o su valor predeterminado), debe
existir en el controlador al que se hace referencia en asp-controller .
asp-route-{valor}
El atributo asp-route-{value} permite indicar un prefijo de ruta comodín. Cualquier valor que ocupe el
marcador de posición {value} se interpretará como un parámetro de ruta potencial. Si no se encuentra
ninguna ruta predeterminada, este prefijo de ruta se anexará al atributo href generado como valor y
parámetro de solicitud. En caso contrario, se sustituirá en la plantilla de ruta.
Observe la siguiente acción del controlador:
public IActionResult AnchorTagHelper(int id)
{
var speaker = new Speaker
{
SpeakerId = id
};
return View(speaker);
}
app.UseMvc(routes =>
{
// need route and attribute on controller: [Area("Blogs")]
routes.MapRoute(name: "areaRoute",
template: "{area:exists}/{controller=Home}/{action=Index}");
La vista de MVC usa el modelo, proporcionado por la acción, como se indica a continuación:
@model Speaker
<!DOCTYPE html>
<html>
<body>
<a asp-controller="Speaker"
asp-action="Detail"
asp-route-id="@Model.SpeakerId">SpeakerId: @Model.SpeakerId</a>
</body>
</html>
Se hace coincidir el marcador de posición {id?} de la ruta predeterminada. El código HTML generado:
Supongamos que el prefijo de ruta no forma parte de la plantilla de enrutamiento coincidente, al igual que con
la siguiente vista de MVC:
@model Speaker
<!DOCTYPE html>
<html>
<body>
<a asp-controller="Speaker"
asp-action="Detail"
asp-route-speakerid="@Model.SpeakerId">SpeakerId: @Model.SpeakerId</a>
<body>
</html>
asp-route
El atributo asp-route se usa para crear una dirección URL que se vincula directamente con una ruta con
nombre. Mediante atributos de enrutamiento puede asignarse un nombre a una ruta tal y como se muestra en
SpeakerController y usarse en su acción Evaluations :
[Route("/Speaker/Evaluations",
Name = "speakerevals")]
public IActionResult Evaluations() => View();
La aplicación auxiliar de etiquetas delimitadoras genera una ruta directamente a esa acción de controlador
mediante la dirección URL /Speaker/Evaluations. El código HTML generado:
asp-all-route-data
El atributo asp-all-route-data permite crear un diccionario de pares clave-valor. La clave es el nombre del
parámetro, mientras que el valor es el valor del parámetro.
En el ejemplo siguiente se inicializa un diccionario y se pasa a una vista de Razor. Los datos también se
podrían pasar con el modelo.
@{
var parms = new Dictionary<string, string>
{
{ "speakerId", "11" },
{ "currentYear", "true" }
};
}
<a asp-route="speakerevalscurrent"
asp-all-route-data="parms">Speaker Evaluations</a>
Se acopla el diccionario asp-all-route-data para generar una cadena de consulta que cumpla los requisitos
de la acción Evaluations sobrecargada:
[Route("/Speaker/EvaluationsCurrent",
Name = "speakerevalscurrent")]
public IActionResult Evaluations(
int speakerId,
bool currentYear) => View();
Si alguna de las claves del diccionario coincide con los parámetros de ruta, esos valores se sustituirán en la
ruta según corresponda. Los demás valores no coincidentes se generarán como parámetros de solicitud.
asp-fragment
El atributo asp-fragment define un fragmento de dirección URL que se anexará a la dirección URL. La
aplicación auxiliar de etiquetas delimitadoras agrega el carácter de almohadilla (#). Observe el siguiente
marcado:
<a asp-controller="Speaker"
asp-action="Evaluations"
asp-fragment="SpeakerEvaluations">Speaker Evaluations</a>
Las etiquetas hash son útiles al crear aplicaciones del lado cliente. Por ejemplo, se pueden usar para el
marcado y la búsqueda en JavaScript.
asp-area
El atributo asp-area establece el nombre de área que se usa para establecer la ruta adecuada. En el siguiente
ejemplo se muestra cómo el atributo de área provoca una reasignación de rutas. Al establecer asp-area en
"Blogs", el directorio Areas/Blogs se prefija en las rutas de los controladores y vistas asociados de esta etiqueta
delimitadora.
<Nombre del proyecto>
wwwroot
Áreas
Blogs
Controladores
HomeController.cs
Vistas
Página principal
AboutBlog.cshtml
Index.cshtml
_ViewStart.cshtml
Controladores
Dada la jerarquía de directorios anterior, el marcado para hacer referencia al archivo AboutBlog.cshtml es el
siguiente:
<a asp-area="Blogs"
asp-controller="Home"
asp-action="AboutBlog">About Blog</a>
TIP
Para que las áreas funcionen en una aplicación de MVC, la plantilla de ruta debe incluir una referencia al área, en el caso
de que exista. Esta plantilla se representa mediante el segundo parámetro de la llamada de método routes.MapRoute
en Startup.Configure:[!code-csharp]
asp-protocol
El atributo asp-protocol sirve para especificar un protocolo (por ejemplo, https ) en la dirección URL. Por
ejemplo:
<a asp-protocol="https"
asp-controller="Home"
asp-action="About">About</a>
<a href="https://localhost/Home/About">About</a>
El nombre de host del ejemplo es localhost, pero la aplicación auxiliar de etiquetas delimitadoras usa el
dominio público del sitio web al generar la dirección URL.
asp-host
El atributo asp-host sirve para especificar un nombre de host en la dirección URL. Por ejemplo:
<a asp-protocol="https"
asp-host="microsoft.com"
asp-controller="Home"
asp-action="About">About</a>
<a href="https://microsoft.com/Home/About">About</a>
asp-page
El atributo asp-page se usa con las páginas de Razor. Úselo para establecer el valor del atributo href de una
etiqueta delimitadora en una página específica. La dirección URL se crea al prefijar el nombre de la página con
una barra diagonal ("/").
En el ejemplo siguiente se señala a la página de Razor de asistentes:
<a asp-page="/Attendee">All Attendees</a>
El atributo asp-page es mutuamente excluyente con los atributos asp-route , asp-controller y asp-action .
Pero se puede usar asp-page con asp-route-{value} para controlar el enrutamiento, como se muestra en el
siguiente marcado:
<a asp-page="/Attendee"
asp-route-attendeeid="10">View Attendee</a>
asp-page-handler
El atributo asp-page-handler se usa con las páginas de Razor. Está diseñado para crear un vínculo con
controladores de página específicos.
Observe el siguiente controlador de página:
El marcado asociado del modelo de página se vincula con el controlador de página OnGetProfile . Tenga en
cuenta que el prefijo On<Verb> del nombre de método del controlador de página se omite en el valor del
atributo asp-page-handler . Si se tratara de un método asincrónico, también se omitiría el sufijo Async .
<a asp-page="/Attendee"
asp-page-handler="Profile"
asp-route-attendeeid="12">Attendee Profile</a>
Recursos adicionales
Áreas
Introducción a las páginas de Razor
Aplicación auxiliar de etiquetas de caché en ASP.NET
Core MVC
25/06/2018 • 9 minutes to read • Edit Online
<cache>@DateTime.Now</cache>
La primera solicitud a la página que contiene CacheTagHelper mostrará la fecha y hora actuales. Las solicitudes
adicionales mostrarán el valor almacenado en caché hasta que la memoria caché expira (el valor predeterminado
es 20 minutos) o se expulsa por la presión de memoria.
Puede establecer la duración de la caché con los siguientes atributos:
"false"
Determina si el contenido incluido en la aplicación auxiliar de etiqueta de caché se almacena en caché. El valor
predeterminado es true . Si se establece en false , la aplicación auxiliar de etiqueta de caché no tiene ningún
efecto de almacenamiento en caché en la salida representada.
Ejemplo:
<cache enabled="true">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>
expires-on
TIPO DE ATRIBUTO VALOR DE EJEMPLO
Establece una fecha de expiración absoluta. En el ejemplo siguiente, se almacenará en memoria caché el
contenido de la aplicación auxiliar de etiqueta de caché hasta las 17:02 del 29 de enero de 2025.
Ejemplo:
expires-after
TIPO DE ATRIBUTO VALOR DE EJEMPLO
TimeSpan "@TimeSpan.FromSeconds(120)"
Establece el período de tiempo desde la primera solicitud para almacenar en caché el contenido.
Ejemplo:
<cache expires-after="@TimeSpan.FromSeconds(120)">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>
expires-sliding
TIPO DE ATRIBUTO VALOR DE EJEMPLO
TimeSpan "@TimeSpan.FromSeconds(60)"
Establece el tiempo en que se debe expulsar una entrada de caché si no se ha accedido a ella.
Ejemplo:
<cache expires-sliding="@TimeSpan.FromSeconds(60)">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>
vary-by-header
TIPO DE ATRIBUTO VALORES DE EJEMPLO
String "User-Agent"
"User-Agent,content-encoding"
Acepta un valor de encabezado único o una lista separada por comas con los valores de encabezado que
desencadenan una actualización de la caché cuando cambian. En el ejemplo siguiente se supervisa el valor del
encabezado User-Agent . En el ejemplo se almacenará en memoria caché el contenido de cada User-Agent que
se muestre al servidor web.
Ejemplo:
<cache vary-by-header="User-Agent">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>
vary-by-query
TIPO DE ATRIBUTO VALORES DE EJEMPLO
String "Make"
"Make,Model"
Acepta un valor de encabezado único o una lista separada por comas con los valores de encabezado que
desencadenan una actualización de la caché cuando cambia el valor de encabezado. En el ejemplo siguiente se
examinan los valores de Make y Model .
Ejemplo:
<cache vary-by-query="Make,Model">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>
vary-by-route
TIPO DE ATRIBUTO VALORES DE EJEMPLO
String "Make"
"Make,Model"
Acepta un valor de encabezado único o una lista separada por comas con los valores de encabezado que
desencadenan una actualización de la caché cuando se produce un cambio en los valores de parámetro de datos
de ruta. Ejemplo:
Startup.cs
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{Make?}/{Model?}");
Index.cshtml
<cache vary-by-route="Make,Model">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>
vary-by-cookie
TIPO DE ATRIBUTO VALORES DE EJEMPLO
String ".AspNetCore.Identity.Application"
TIPO DE ATRIBUTO VALORES DE EJEMPLO
".AspNetCore.Identity.Application,HairColor"
Acepta un valor de encabezado único o una lista separada por comas con los valores de encabezado que
desencadenan una actualización de la caché cuando cambian los valores de encabezado. En el ejemplo siguiente
se examina la cookie asociada con ASP.NET Identity. Cuando un usuario se autentica, la cookie de solicitud que se
establece desencadena una actualización de la caché.
Ejemplo:
<cache vary-by-cookie=".AspNetCore.Identity.Application">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>
vary-by-user
TIPO DE ATRIBUTO VALORES DE EJEMPLO
Booleano "true"
Especifica si debe restablecerse la memoria caché cuando el usuario que ha iniciado la sesión (o la entidad de
seguridad del contexto) cambia. El usuario actual también se conoce como entidad de seguridad del contexto de
solicitud y puede verse en una vista Razor mediante una referencia a @User.Identity.Name .
En el ejemplo siguiente se examina el usuario conectado actualmente.
Ejemplo:
<cache vary-by-user="true">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>
Con este atributo, se mantiene el contenido en caché a través de un ciclo de inicio y cierre de sesión. Al utilizar
vary-by-user="true" , una acción de inicio y cierre de sesión invalida la caché para el usuario autenticado. Se
invalida la memoria caché porque se genera un nuevo valor único de cookie al iniciar sesión. Se mantiene la
memoria caché para el estado anónimo cuando no hay ninguna cookie o la cookie ha expirado. Esto significa que
si ningún usuario ha iniciado sesión, se mantendrá la memoria caché.
vary-by
TIPO DE ATRIBUTO VALORES DE EJEMPLO
String "@Model"
Permite la personalización de los datos que se almacenan en caché. Cuando el objeto al que hace referencia el
valor de cadena del atributo cambia, el contenido de la aplicación auxiliar de etiqueta de caché se actualiza. A
menudo se asignan a este atributo una concatenación de cadenas de valores del modelo. De hecho, eso significa
que una actualización de cualquiera de los valores concatenados invalida la memoria caché.
En el ejemplo siguiente se supone que el método de controlador que representa la vista suma el valor del entero
de los dos parámetros de ruta, myParam1 y myParam2 , y devuelve el resultado como la propiedad de modelo
simple. Cuando se cambia esta suma, el contenido de la aplicación auxiliar de etiqueta de caché se representa y
almacena en caché de nuevo.
Ejemplo:
Acción:
Index.cshtml
<cache vary-by="@Model"">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>
priority
TIPO DE ATRIBUTO VALORES DE EJEMPLO
CacheItemPriority "High"
"Low"
"NeverRemove"
"Normal"
Proporciona instrucciones de expulsión de caché para el proveedor de caché integrado. El servidor web expulsará
primero las entradas de caché Low cuando esté bajo presión de memoria.
Ejemplo:
<cache priority="High">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>
Recursos adicionales
Almacenamiento en caché en memoria
Introducción a Identity
Aplicación auxiliar de etiquetas de caché distribuida
en ASP.NET Core
25/06/2018 • 3 minutes to read • Edit Online
cadena "my-distributed-cache-unique-key-101"
El atributo name obligatorio se usa como clave de la caché almacenada para cada instancia de una aplicación
auxiliar de etiquetas de caché distribuida. A diferencia de la aplicación auxiliar de etiquetas de caché básica, que
asigna una clave a cada instancia de la aplicación auxiliar de etiquetas de caché en función del nombre de la
página de Razor y la ubicación de la aplicación auxiliar de etiquetas en la página de Razor, la aplicación auxiliar de
etiquetas de caché distribuida solo basa su clave en el atributo name .
Ejemplo de uso:
<distributed-cache name="my-distributed-cache-unique-key-101">
Time Inside Cache Tag Helper: @DateTime.Now
</distributed-cache>
Recursos adicionales
Aplicación auxiliar de etiquetas de caché en ASP.NET Core MVC
Inserción de dependencias en ASP.NET Core
Trabajar con una memoria caché distribuida en ASP.NET Core
Almacenar en memoria caché en memoria en el núcleo de ASP.NET
Introducción a la identidad de un núcleo de ASP.NET
Aplicación auxiliar de etiquetas de entorno en
ASP.NET Core
25/06/2018 • 2 minutes to read • Edit Online
<environment names="Staging,Production">
<strong>HostingEnvironment.EnvironmentName is Staging or Production</strong>
</environment>
<environment include="Staging,Production">
<strong>HostingEnvironment.EnvironmentName is Staging or Production</strong>
</environment>
<environment exclude="Development">
<strong>HostingEnvironment.EnvironmentName is Staging or Production</strong>
</environment>
Recursos adicionales
Usar varios entornos en ASP.NET Core
Inserción de dependencias en ASP.NET Core
Aplicaciones auxiliares de etiquetas en formularios de
ASP.NET Core
19/06/2018 • 29 minutes to read • Edit Online
El tiempo de ejecución MVC genera el valor de atributo action de los atributos de aplicación auxiliar de etiquetas
Form asp-controller y asp-action . La aplicación auxiliar de etiquetas Form genera también un token de
comprobación de solicitudes oculto que impide que se falsifiquen solicitudes entre sitios (cuando se usa con el
atributo [ValidateAntiForgeryToken] en el método de acción HTTP Post). Proteger un elemento HTML Form puro
de la falsificación de solicitudes entre sitios no es tarea fácil, y la aplicación auxiliar de etiquetas Form presta este
servicio.
Uso de una ruta con nombre
El atributo de aplicación auxiliar de etiquetas asp-route puede generar también el marcado del atributo HTML
action . Una aplicación con una ruta denominada register podría usar el siguiente marcado para la página de
registro:
Muchas de las vistas de la carpeta Views/Account (que se genera cuando se crea una aplicación web con Cuentas
de usuario individuales) contienen el atributo asp-route-returnurl:
NOTE
Con las plantillas integradas, returnUrl se rellena automáticamente solo cuando alguien intenta obtener acceso a un
recurso autorizado, pero no se ha autenticado o no tiene autorización. Si se intenta realizar un acceso no autorizado, el
middleware de seguridad redirige a la página de inicio de sesión con returnUrl configurado.
Type expected
'RegisterViewModel' does not contain a definition for 'Email' and no
extension method 'Email' accepting a first argument of type 'RegisterViewModel'
could be found (are you missing a using directive or an assembly reference?)
La aplicación auxiliar de etiquetas Input establece el atributo HTML type en función del tipo .NET. En la siguiente
tabla se enumeran algunos tipos .NET habituales y el tipo HTML generado correspondiente (no incluimos aquí
todos los tipos .NET).
Bool type=”checkbox”
String type=”text”
DateTime type=”datetime”
Byte type=”number”
En la siguiente tabla se muestran algunos atributos de anotación de datos comunes que la aplicación auxiliar de
etiquetas Input asignará a tipos de entrada concretos (no incluimos aquí todos los atributo de validación):
[EmailAddress] type=”email”
[Url] type=”url”
[HiddenInput] type=”hidden”
[Phone] type=”tel”
[DataType(DataType.Password)] type=”password”
[DataType(DataType.Date)] type=”date”
[DataType(DataType.Time)] type=”time”
Ejemplo:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
}
@model RegisterViewModel
Las anotaciones de datos que se aplican a las propiedades Email y Password generan metadatos en el modelo. La
aplicación auxiliar de etiquetas Input usa esos metadatos del modelo y genera atributos HTML5 data-val-* . Vea
Model Validation (Validación del modelo). Estos atributos describen los validadores que se van a adjuntar a los
campos de entrada, lo que proporciona HTML5 discreto y validación de jQuery. Los atributos discretos tienen el
formato data-val-rule="Error Message" , donde "rule" es el nombre de la regla de validación (como
data-val-required , data-val-email , data-val-maxlength , etc.). Si aparece un mensaje de error en el atributo, se
mostrará como el valor del atributo data-val-rule . También hay atributos con el formato
data-val-ruleName-argumentName="argumentValue" que aportan más información sobre la regla, por ejemplo,
data-val-maxlength-max="1024" .
Nombres de expresión
El valor del atributo asp-for es una ModelExpression y la parte de la derecha de una expresión lambda. Por tanto,
asp-for="Property1" se convierte en m => m.Property1 en el código generado, motivo por el que no es necesario
incluir el prefijo Model . Puede usar el carácter "@" para iniciar una expresión insertada y moverla antes de `m.`:
</span><span class="sxs-lookup">You can use the "@" character to start an inline expression and move before
the m. :
@{
var joe = "Joe";
}
<input asp-for="@joe" />
[DataType(DataType.Password)]
public string Password { get; set; }
@model RegisterAddressViewModel
El método de acción:
El siguiente código de Razor muestra cómo se tiene acceso a un elemento Color concreto:
@model Person
@{
var index = (int)ViewData["index"];
}
@model string
<label asp-for="@Model"></label>
<input asp-for="@Model" /> <br />
@model List<ToDoItem>
</table>
<button type="submit">Save</button>
</form>
La plantilla Views/Shared/EditorTemplates/ToDoItem.cshtml:
@model ToDoItem
<td>
<label asp-for="@Model.Name"></label>
@Html.DisplayFor(model => model.Name)
</td>
<td>
<input asp-for="@Model.IsDone" />
</td>
@*
This template replaces the following Razor which evaluates the indexer three times.
<td>
<label asp-for="@Model[i].Name"></label>
@Html.DisplayFor(model => model[i].Name)
</td>
<td>
<input asp-for="@Model[i].IsDone" />
</td>
*@
NOTE
Use siempre for (y no foreach ) para iterar por una lista. Evaluar un indizador en una expresión de LINQ puede ser
costoso, con lo cual esa posibilidad hay que reducirla al mínimo.
NOTE
El código de ejemplo comentado anterior muestra cómo reemplazaríamos la expresión lambda por el operador @ para tener
acceso a cada elemento ToDoItem de la lista.
Ejemplo:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class DescriptionViewModel
{
[MinLength(5)]
[MaxLength(1024)]
public string Description { get; set; }
}
}
@model DescriptionViewModel
Label Tag Helper proporciona las siguientes ventajas con respecto a un elemento HTML Label puro:
Obtendrá automáticamente el valor de la etiqueta descriptiva del atributo Display . El nombre para mostrar
que se busca puede cambiar con el tiempo y la combinación del atributo Display , y la aplicación auxiliar de
etiquetas Label aplicará el elemento Display en cualquier lugar donde se use.
El código fuente contiene menos marcado.
Permite establecer tipado fuerte con la propiedad de modelo.
Ejemplo:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class SimpleViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
}
}
@model SimpleViewModel
La aplicación auxiliar de etiquetas Label genera el valor de atributo for de "Email", que es el identificador asociado
al elemento <input> . Las aplicaciones auxiliares de etiqueta generan elementos id y for coherentes para que se
puedan asociar correctamente. El título de este ejemplo proviene del atributo Display . Si el modelo no contuviera
un atributo Display , el título sería el nombre de propiedad de la expresión.
Validation Message Tag Helper se usa con el atributo asp-validation-for en un elemento HTML span.
<span asp-validation-for="Email"></span>
<span class="field-validation-valid"
data-valmsg-for="Email"
data-valmsg-replace="true"></span>
Validation Message Tag Helper suele usarse por lo general después de una aplicación auxiliar de etiquetas Input
en la misma propiedad. Gracias a esto, se mostrarán todos los mensajes de error de validación cerca de la entrada
que produjo el error.
NOTE
Debe tener una vista con las referencias de script de JavaScript y de jQuery adecuadas para la validación del lado cliente. Para
más información, vea Introduction to model validation in ASP.NET Core MVC (Introducción a la validación de modelos en
ASP.NET Core MVC).
Cuando se produce un error de validación del lado servidor (por ejemplo, porque haya una validación del lado
servidor personalizada o porque la validación del lado cliente esté deshabilitada), MVC pone ese mensaje de error
como cuerpo del elemento <span> .
Validation Summary Tag Helper se usa para mostrar un resumen de los mensajes de validación. El valor de atributo
asp-validation-summary puede ser cualquiera de los siguientes:
ValidationSummary.ModelOnly Modelo
ValidationSummary.None Ninguna
Ejemplo
En el siguiente ejemplo, el modelo de datos se complementa con atributos DataAnnotation , lo que genera mensajes
de error de validación sobre el elemento <input> . Cuando se produce un error de validación, la aplicación auxiliar
de etiquetas de validación muestra el mensaje de error:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
}
@model RegisterViewModel
Ejemplo:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace FormsTagHelper.ViewModels
{
public class CountryViewModel
{
public string Country { get; set; }
El método Index inicializa CountryViewModel , establece el país seleccionado y lo pasa a la vista Index .
La vista Index :
@model CountryViewModel
NOTE
No se recomienda usar ViewBag o ViewData con la aplicación auxiliar de etiquetas Select. Un modelo de vista es más eficaz
a la hora de proporcionar metadatos MVC y suele ser menos problemático.
El valor de atributo asp-for es un caso especial y no necesita un prefijo Model , mientras que los otros atributos de
aplicación auxiliar de etiquetas sí (como asp-items ).
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}
@model CountryEnumViewModel
Puede complementar la lista de enumeradores con el atributo Display para obtener una interfaz de usuario más
completa:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}
Agrupamiento de opciones
El elemento HTML <optgroup> se genera cuando el modelo de vista contiene uno o varios objetos
SelectListGroup .
CountryViewModelGroup agrupa los elementos SelectListItem en los grupos "North America" y "Europe":
public class CountryViewModelGroup
{
public CountryViewModelGroup()
{
var NorthAmericaGroup = new SelectListGroup { Name = "North America" };
var EuropeGroup = new SelectListGroup { Name = "Europe" };
Selección múltiple
La aplicación auxiliar de etiquetas Select generará automáticamente el atributo multiple = "multiple" si la propiedad
especificada en el atributo asp-for es IEnumerable . Por ejemplo, si tenemos el siguiente modelo:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace FormsTagHelper.ViewModels
{
public class CountryViewModelIEnumerable
{
public IEnumerable<string> CountryCodes { get; set; }
Sin selección
Si ve que usa la opción "sin especificar" en varias páginas, puede crear una plantilla para no tener que repetir el
código HTML:
@model CountryViewModel
La plantilla Views/Shared/EditorTemplates/CountryViewModel.cshtml:
@model CountryViewModel
La posibilidad de agregar elementos HTML <option> no se limita exclusivamente a los casos en los que no se
seleccionada nada. Por ejemplo, el método de acción y vista siguientes generarán un código HTML similar al
código anterior:
Se seleccionará el elemento <option> correcto (que contenga el atributo selected="selected" ) en función del valor
real de Country .
Recursos adicionales
Aplicaciones auxiliares de etiquetas
HTML Form element (Elemento HTML Form)
Request Verification Token (Token de comprobación de solicitudes)
Enlace de modelos
Introduction to model validation in ASP.NET Core MVC (Introducción a la validación de modelos en ASP.NET
Core MVC )
IAttributeAdapter Interface (Interfaz IAttributeAdapter)
Fragmentos de código de este documento
Aplicación auxiliar de etiquetas de imagen en
ASP.NET Core
25/06/2018 • 3 minutes to read • Edit Online
Si el origen de la imagen ( src ) es un archivo estático en el servidor web del host, se anexa una cadena única de
bloqueo de la caché como parámetro de consulta a ese origen de la imagen. De este modo, si el archivo en el
servidor web del host cambia, se generará una dirección URL de solicitud única que incluye el parámetro de
solicitud actualizada. La cadena de limpieza de memoria caché es un valor único que representa el valor hash del
archivo de imagen estático.
Si el origen de la imagen ( src ) no es un archivo estático (por ejemplo, es una dirección URL remota o se trata de
un archivo que no existe en el servidor) el atributo src de la etiqueta <img> se genera sin parámetro de cadena
de consulta de limpieza de caché.
<img src="~/images/asplogo.png"
asp-append-version="true" />
<img
src="/images/asplogo.png?v=Kl_dqr9NVtnMdsM2MUg4qthUnWZm5T1fCEimBPWDNgM"/>
El valor asignado al parámetro v es el valor hash del archivo almacenado en disco. Si el servidor web no es capaz
de obtener acceso de lectura al archivo estático al que se hace referencia, no se agregará ningún parámetro v al
atributo src .
src
Para activar la aplicación auxiliar de etiquetas de imagen, se requiere el atributo src en el elemento <img> .
NOTE
La aplicación auxiliar de etiquetas de imagen usa el proveedor Cache en el servidor web local para almacenar el valor de
Sha512 calculado de un archivo determinado. Si el archivo se vuelve a solicitar, no es necesario volver a calcular Sha512 . La
memoria caché queda invalidada por un monitor del archivo que se adjunta al archivo cuando el valor de Sha512 del
archivo se calcula.
Recursos adicionales
Almacenar en memoria caché en memoria en el núcleo de ASP.NET
Aplicaciones auxiliares de etiquetas en formularios de
ASP.NET Core
19/06/2018 • 29 minutes to read • Edit Online
El tiempo de ejecución MVC genera el valor de atributo action de los atributos de aplicación auxiliar de etiquetas
Form asp-controller y asp-action . La aplicación auxiliar de etiquetas Form genera también un token de
comprobación de solicitudes oculto que impide que se falsifiquen solicitudes entre sitios (cuando se usa con el
atributo [ValidateAntiForgeryToken] en el método de acción HTTP Post). Proteger un elemento HTML Form puro
de la falsificación de solicitudes entre sitios no es tarea fácil, y la aplicación auxiliar de etiquetas Form presta este
servicio.
Uso de una ruta con nombre
El atributo de aplicación auxiliar de etiquetas asp-route puede generar también el marcado del atributo HTML
action . Una aplicación con una ruta denominada register podría usar el siguiente marcado para la página de
registro:
Muchas de las vistas de la carpeta Views/Account (que se genera cuando se crea una aplicación web con Cuentas
de usuario individuales) contienen el atributo asp-route-returnurl:
NOTE
Con las plantillas integradas, returnUrl se rellena automáticamente solo cuando alguien intenta obtener acceso a un
recurso autorizado, pero no se ha autenticado o no tiene autorización. Si se intenta realizar un acceso no autorizado, el
middleware de seguridad redirige a la página de inicio de sesión con returnUrl configurado.
Type expected
'RegisterViewModel' does not contain a definition for 'Email' and no
extension method 'Email' accepting a first argument of type 'RegisterViewModel'
could be found (are you missing a using directive or an assembly reference?)
La aplicación auxiliar de etiquetas Input establece el atributo HTML type en función del tipo .NET. En la siguiente
tabla se enumeran algunos tipos .NET habituales y el tipo HTML generado correspondiente (no incluimos aquí
todos los tipos .NET).
Bool type=”checkbox”
String type=”text”
DateTime type=”datetime”
Byte type=”number”
En la siguiente tabla se muestran algunos atributos de anotación de datos comunes que la aplicación auxiliar de
etiquetas Input asignará a tipos de entrada concretos (no incluimos aquí todos los atributo de validación):
[EmailAddress] type=”email”
[Url] type=”url”
[HiddenInput] type=”hidden”
[Phone] type=”tel”
[DataType(DataType.Password)] type=”password”
[DataType(DataType.Date)] type=”date”
[DataType(DataType.Time)] type=”time”
Ejemplo:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
}
@model RegisterViewModel
Las anotaciones de datos que se aplican a las propiedades Email y Password generan metadatos en el modelo. La
aplicación auxiliar de etiquetas Input usa esos metadatos del modelo y genera atributos HTML5 data-val-* . Vea
Model Validation (Validación del modelo). Estos atributos describen los validadores que se van a adjuntar a los
campos de entrada, lo que proporciona HTML5 discreto y validación de jQuery. Los atributos discretos tienen el
formato data-val-rule="Error Message" , donde "rule" es el nombre de la regla de validación (como
data-val-required , data-val-email , data-val-maxlength , etc.). Si aparece un mensaje de error en el atributo, se
mostrará como el valor del atributo data-val-rule . También hay atributos con el formato
data-val-ruleName-argumentName="argumentValue" que aportan más información sobre la regla, por ejemplo,
data-val-maxlength-max="1024" .
Nombres de expresión
El valor del atributo asp-for es una ModelExpression y la parte de la derecha de una expresión lambda. Por tanto,
asp-for="Property1" se convierte en m => m.Property1 en el código generado, motivo por el que no es necesario
incluir el prefijo Model . Puede usar el carácter "@" para iniciar una expresión insertada y moverla antes de `m.`:
</span><span class="sxs-lookup">You can use the "@" character to start an inline expression and move before
the m. :
@{
var joe = "Joe";
}
<input asp-for="@joe" />
[DataType(DataType.Password)]
public string Password { get; set; }
@model RegisterAddressViewModel
El método de acción:
El siguiente código de Razor muestra cómo se tiene acceso a un elemento Color concreto:
@model Person
@{
var index = (int)ViewData["index"];
}
@model string
<label asp-for="@Model"></label>
<input asp-for="@Model" /> <br />
@model List<ToDoItem>
</table>
<button type="submit">Save</button>
</form>
La plantilla Views/Shared/EditorTemplates/ToDoItem.cshtml:
@model ToDoItem
<td>
<label asp-for="@Model.Name"></label>
@Html.DisplayFor(model => model.Name)
</td>
<td>
<input asp-for="@Model.IsDone" />
</td>
@*
This template replaces the following Razor which evaluates the indexer three times.
<td>
<label asp-for="@Model[i].Name"></label>
@Html.DisplayFor(model => model[i].Name)
</td>
<td>
<input asp-for="@Model[i].IsDone" />
</td>
*@
NOTE
Use siempre for (y no foreach ) para iterar por una lista. Evaluar un indizador en una expresión de LINQ puede ser
costoso, con lo cual esa posibilidad hay que reducirla al mínimo.
NOTE
El código de ejemplo comentado anterior muestra cómo reemplazaríamos la expresión lambda por el operador @ para tener
acceso a cada elemento ToDoItem de la lista.
Ejemplo:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class DescriptionViewModel
{
[MinLength(5)]
[MaxLength(1024)]
public string Description { get; set; }
}
}
@model DescriptionViewModel
Label Tag Helper proporciona las siguientes ventajas con respecto a un elemento HTML Label puro:
Obtendrá automáticamente el valor de la etiqueta descriptiva del atributo Display . El nombre para mostrar
que se busca puede cambiar con el tiempo y la combinación del atributo Display , y la aplicación auxiliar de
etiquetas Label aplicará el elemento Display en cualquier lugar donde se use.
El código fuente contiene menos marcado.
Permite establecer tipado fuerte con la propiedad de modelo.
Ejemplo:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class SimpleViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
}
}
@model SimpleViewModel
La aplicación auxiliar de etiquetas Label genera el valor de atributo for de "Email", que es el identificador asociado
al elemento <input> . Las aplicaciones auxiliares de etiqueta generan elementos id y for coherentes para que se
puedan asociar correctamente. El título de este ejemplo proviene del atributo Display . Si el modelo no contuviera
un atributo Display , el título sería el nombre de propiedad de la expresión.
Validation Message Tag Helper se usa con el atributo asp-validation-for en un elemento HTML span.
<span asp-validation-for="Email"></span>
<span class="field-validation-valid"
data-valmsg-for="Email"
data-valmsg-replace="true"></span>
Validation Message Tag Helper suele usarse por lo general después de una aplicación auxiliar de etiquetas Input
en la misma propiedad. Gracias a esto, se mostrarán todos los mensajes de error de validación cerca de la entrada
que produjo el error.
NOTE
Debe tener una vista con las referencias de script de JavaScript y de jQuery adecuadas para la validación del lado cliente. Para
más información, vea Introduction to model validation in ASP.NET Core MVC (Introducción a la validación de modelos en
ASP.NET Core MVC).
Cuando se produce un error de validación del lado servidor (por ejemplo, porque haya una validación del lado
servidor personalizada o porque la validación del lado cliente esté deshabilitada), MVC pone ese mensaje de error
como cuerpo del elemento <span> .
Validation Summary Tag Helper se usa para mostrar un resumen de los mensajes de validación. El valor de atributo
asp-validation-summary puede ser cualquiera de los siguientes:
ValidationSummary.ModelOnly Modelo
ValidationSummary.None Ninguna
Ejemplo
En el siguiente ejemplo, el modelo de datos se complementa con atributos DataAnnotation , lo que genera mensajes
de error de validación sobre el elemento <input> . Cuando se produce un error de validación, la aplicación auxiliar
de etiquetas de validación muestra el mensaje de error:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
}
@model RegisterViewModel
Ejemplo:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace FormsTagHelper.ViewModels
{
public class CountryViewModel
{
public string Country { get; set; }
El método Index inicializa CountryViewModel , establece el país seleccionado y lo pasa a la vista Index .
La vista Index :
@model CountryViewModel
NOTE
No se recomienda usar ViewBag o ViewData con la aplicación auxiliar de etiquetas Select. Un modelo de vista es más eficaz
a la hora de proporcionar metadatos MVC y suele ser menos problemático.
El valor de atributo asp-for es un caso especial y no necesita un prefijo Model , mientras que los otros atributos de
aplicación auxiliar de etiquetas sí (como asp-items ).
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}
@model CountryEnumViewModel
Puede complementar la lista de enumeradores con el atributo Display para obtener una interfaz de usuario más
completa:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}
Agrupamiento de opciones
El elemento HTML <optgroup> se genera cuando el modelo de vista contiene uno o varios objetos
SelectListGroup .
CountryViewModelGroup agrupa los elementos SelectListItem en los grupos "North America" y "Europe":
public class CountryViewModelGroup
{
public CountryViewModelGroup()
{
var NorthAmericaGroup = new SelectListGroup { Name = "North America" };
var EuropeGroup = new SelectListGroup { Name = "Europe" };
Selección múltiple
La aplicación auxiliar de etiquetas Select generará automáticamente el atributo multiple = "multiple" si la propiedad
especificada en el atributo asp-for es IEnumerable . Por ejemplo, si tenemos el siguiente modelo:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace FormsTagHelper.ViewModels
{
public class CountryViewModelIEnumerable
{
public IEnumerable<string> CountryCodes { get; set; }
Sin selección
Si ve que usa la opción "sin especificar" en varias páginas, puede crear una plantilla para no tener que repetir el
código HTML:
@model CountryViewModel
La plantilla Views/Shared/EditorTemplates/CountryViewModel.cshtml:
@model CountryViewModel
La posibilidad de agregar elementos HTML <option> no se limita exclusivamente a los casos en los que no se
seleccionada nada. Por ejemplo, el método de acción y vista siguientes generarán un código HTML similar al
código anterior:
Se seleccionará el elemento <option> correcto (que contenga el atributo selected="selected" ) en función del valor
real de Country .
Recursos adicionales
Aplicaciones auxiliares de etiquetas
HTML Form element (Elemento HTML Form)
Request Verification Token (Token de comprobación de solicitudes)
Enlace de modelos
Introduction to model validation in ASP.NET Core MVC (Introducción a la validación de modelos en ASP.NET
Core MVC )
IAttributeAdapter Interface (Interfaz IAttributeAdapter)
Fragmentos de código de este documento
Aplicaciones auxiliares de etiquetas en formularios de
ASP.NET Core
19/06/2018 • 29 minutes to read • Edit Online
El tiempo de ejecución MVC genera el valor de atributo action de los atributos de aplicación auxiliar de etiquetas
Form asp-controller y asp-action . La aplicación auxiliar de etiquetas Form genera también un token de
comprobación de solicitudes oculto que impide que se falsifiquen solicitudes entre sitios (cuando se usa con el
atributo [ValidateAntiForgeryToken] en el método de acción HTTP Post). Proteger un elemento HTML Form puro
de la falsificación de solicitudes entre sitios no es tarea fácil, y la aplicación auxiliar de etiquetas Form presta este
servicio.
Uso de una ruta con nombre
El atributo de aplicación auxiliar de etiquetas asp-route puede generar también el marcado del atributo HTML
action . Una aplicación con una ruta denominada register podría usar el siguiente marcado para la página de
registro:
Muchas de las vistas de la carpeta Views/Account (que se genera cuando se crea una aplicación web con Cuentas
de usuario individuales) contienen el atributo asp-route-returnurl:
NOTE
Con las plantillas integradas, returnUrl se rellena automáticamente solo cuando alguien intenta obtener acceso a un
recurso autorizado, pero no se ha autenticado o no tiene autorización. Si se intenta realizar un acceso no autorizado, el
middleware de seguridad redirige a la página de inicio de sesión con returnUrl configurado.
Type expected
'RegisterViewModel' does not contain a definition for 'Email' and no
extension method 'Email' accepting a first argument of type 'RegisterViewModel'
could be found (are you missing a using directive or an assembly reference?)
La aplicación auxiliar de etiquetas Input establece el atributo HTML type en función del tipo .NET. En la siguiente
tabla se enumeran algunos tipos .NET habituales y el tipo HTML generado correspondiente (no incluimos aquí
todos los tipos .NET).
Bool type=”checkbox”
String type=”text”
DateTime type=”datetime”
Byte type=”number”
En la siguiente tabla se muestran algunos atributos de anotación de datos comunes que la aplicación auxiliar de
etiquetas Input asignará a tipos de entrada concretos (no incluimos aquí todos los atributo de validación):
[EmailAddress] type=”email”
[Url] type=”url”
[HiddenInput] type=”hidden”
[Phone] type=”tel”
[DataType(DataType.Password)] type=”password”
[DataType(DataType.Date)] type=”date”
[DataType(DataType.Time)] type=”time”
Ejemplo:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
}
@model RegisterViewModel
Las anotaciones de datos que se aplican a las propiedades Email y Password generan metadatos en el modelo. La
aplicación auxiliar de etiquetas Input usa esos metadatos del modelo y genera atributos HTML5 data-val-* . Vea
Model Validation (Validación del modelo). Estos atributos describen los validadores que se van a adjuntar a los
campos de entrada, lo que proporciona HTML5 discreto y validación de jQuery. Los atributos discretos tienen el
formato data-val-rule="Error Message" , donde "rule" es el nombre de la regla de validación (como
data-val-required , data-val-email , data-val-maxlength , etc.). Si aparece un mensaje de error en el atributo, se
mostrará como el valor del atributo data-val-rule . También hay atributos con el formato
data-val-ruleName-argumentName="argumentValue" que aportan más información sobre la regla, por ejemplo,
data-val-maxlength-max="1024" .
Nombres de expresión
El valor del atributo asp-for es una ModelExpression y la parte de la derecha de una expresión lambda. Por tanto,
asp-for="Property1" se convierte en m => m.Property1 en el código generado, motivo por el que no es necesario
incluir el prefijo Model . Puede usar el carácter "@" para iniciar una expresión insertada y moverla antes de `m.`:
</span><span class="sxs-lookup">You can use the "@" character to start an inline expression and move before
the m. :
@{
var joe = "Joe";
}
<input asp-for="@joe" />
[DataType(DataType.Password)]
public string Password { get; set; }
@model RegisterAddressViewModel
El método de acción:
El siguiente código de Razor muestra cómo se tiene acceso a un elemento Color concreto:
@model Person
@{
var index = (int)ViewData["index"];
}
@model string
<label asp-for="@Model"></label>
<input asp-for="@Model" /> <br />
@model List<ToDoItem>
</table>
<button type="submit">Save</button>
</form>
La plantilla Views/Shared/EditorTemplates/ToDoItem.cshtml:
@model ToDoItem
<td>
<label asp-for="@Model.Name"></label>
@Html.DisplayFor(model => model.Name)
</td>
<td>
<input asp-for="@Model.IsDone" />
</td>
@*
This template replaces the following Razor which evaluates the indexer three times.
<td>
<label asp-for="@Model[i].Name"></label>
@Html.DisplayFor(model => model[i].Name)
</td>
<td>
<input asp-for="@Model[i].IsDone" />
</td>
*@
NOTE
Use siempre for (y no foreach ) para iterar por una lista. Evaluar un indizador en una expresión de LINQ puede ser
costoso, con lo cual esa posibilidad hay que reducirla al mínimo.
NOTE
El código de ejemplo comentado anterior muestra cómo reemplazaríamos la expresión lambda por el operador @ para tener
acceso a cada elemento ToDoItem de la lista.
Ejemplo:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class DescriptionViewModel
{
[MinLength(5)]
[MaxLength(1024)]
public string Description { get; set; }
}
}
@model DescriptionViewModel
Label Tag Helper proporciona las siguientes ventajas con respecto a un elemento HTML Label puro:
Obtendrá automáticamente el valor de la etiqueta descriptiva del atributo Display . El nombre para mostrar
que se busca puede cambiar con el tiempo y la combinación del atributo Display , y la aplicación auxiliar de
etiquetas Label aplicará el elemento Display en cualquier lugar donde se use.
El código fuente contiene menos marcado.
Permite establecer tipado fuerte con la propiedad de modelo.
Ejemplo:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class SimpleViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
}
}
@model SimpleViewModel
La aplicación auxiliar de etiquetas Label genera el valor de atributo for de "Email", que es el identificador asociado
al elemento <input> . Las aplicaciones auxiliares de etiqueta generan elementos id y for coherentes para que se
puedan asociar correctamente. El título de este ejemplo proviene del atributo Display . Si el modelo no contuviera
un atributo Display , el título sería el nombre de propiedad de la expresión.
Validation Message Tag Helper se usa con el atributo asp-validation-for en un elemento HTML span.
<span asp-validation-for="Email"></span>
<span class="field-validation-valid"
data-valmsg-for="Email"
data-valmsg-replace="true"></span>
Validation Message Tag Helper suele usarse por lo general después de una aplicación auxiliar de etiquetas Input
en la misma propiedad. Gracias a esto, se mostrarán todos los mensajes de error de validación cerca de la entrada
que produjo el error.
NOTE
Debe tener una vista con las referencias de script de JavaScript y de jQuery adecuadas para la validación del lado cliente. Para
más información, vea Introduction to model validation in ASP.NET Core MVC (Introducción a la validación de modelos en
ASP.NET Core MVC).
Cuando se produce un error de validación del lado servidor (por ejemplo, porque haya una validación del lado
servidor personalizada o porque la validación del lado cliente esté deshabilitada), MVC pone ese mensaje de error
como cuerpo del elemento <span> .
Validation Summary Tag Helper se usa para mostrar un resumen de los mensajes de validación. El valor de atributo
asp-validation-summary puede ser cualquiera de los siguientes:
ValidationSummary.ModelOnly Modelo
ValidationSummary.None Ninguna
Ejemplo
En el siguiente ejemplo, el modelo de datos se complementa con atributos DataAnnotation , lo que genera mensajes
de error de validación sobre el elemento <input> . Cuando se produce un error de validación, la aplicación auxiliar
de etiquetas de validación muestra el mensaje de error:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
}
@model RegisterViewModel
Ejemplo:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace FormsTagHelper.ViewModels
{
public class CountryViewModel
{
public string Country { get; set; }
El método Index inicializa CountryViewModel , establece el país seleccionado y lo pasa a la vista Index .
La vista Index :
@model CountryViewModel
NOTE
No se recomienda usar ViewBag o ViewData con la aplicación auxiliar de etiquetas Select. Un modelo de vista es más eficaz
a la hora de proporcionar metadatos MVC y suele ser menos problemático.
El valor de atributo asp-for es un caso especial y no necesita un prefijo Model , mientras que los otros atributos de
aplicación auxiliar de etiquetas sí (como asp-items ).
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}
@model CountryEnumViewModel
Puede complementar la lista de enumeradores con el atributo Display para obtener una interfaz de usuario más
completa:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}
Agrupamiento de opciones
El elemento HTML <optgroup> se genera cuando el modelo de vista contiene uno o varios objetos
SelectListGroup .
CountryViewModelGroup agrupa los elementos SelectListItem en los grupos "North America" y "Europe":
public class CountryViewModelGroup
{
public CountryViewModelGroup()
{
var NorthAmericaGroup = new SelectListGroup { Name = "North America" };
var EuropeGroup = new SelectListGroup { Name = "Europe" };
Selección múltiple
La aplicación auxiliar de etiquetas Select generará automáticamente el atributo multiple = "multiple" si la propiedad
especificada en el atributo asp-for es IEnumerable . Por ejemplo, si tenemos el siguiente modelo:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace FormsTagHelper.ViewModels
{
public class CountryViewModelIEnumerable
{
public IEnumerable<string> CountryCodes { get; set; }
Sin selección
Si ve que usa la opción "sin especificar" en varias páginas, puede crear una plantilla para no tener que repetir el
código HTML:
@model CountryViewModel
La plantilla Views/Shared/EditorTemplates/CountryViewModel.cshtml:
@model CountryViewModel
La posibilidad de agregar elementos HTML <option> no se limita exclusivamente a los casos en los que no se
seleccionada nada. Por ejemplo, el método de acción y vista siguientes generarán un código HTML similar al
código anterior:
Se seleccionará el elemento <option> correcto (que contenga el atributo selected="selected" ) en función del valor
real de Country .
Recursos adicionales
Aplicaciones auxiliares de etiquetas
HTML Form element (Elemento HTML Form)
Request Verification Token (Token de comprobación de solicitudes)
Enlace de modelos
Introduction to model validation in ASP.NET Core MVC (Introducción a la validación de modelos en ASP.NET
Core MVC )
IAttributeAdapter Interface (Interfaz IAttributeAdapter)
Fragmentos de código de este documento
Aplicación auxiliar de etiquetas parciales en ASP.NET
Core
25/06/2018 • 4 minutes to read • Edit Online
Información general
La aplicación auxiliar de etiquetas parciales sirve para representar una vista parcial en las páginas de Razor y las
aplicaciones MVC. Tenga en cuenta lo siguiente:
Es necesario ASP.NET Core 2.1 o una versión posterior.
Es una alternativa a la sintaxis de la aplicación auxiliar HTML.
Presenta la vista parcial de forma asincrónica.
Las opciones de la aplicación auxiliar HTML para representar una vista parcial son estas:
@await Html.PartialAsync
@await Html.RenderPartialAsync
@Html.Partial
@Html.RenderPartial
En los ejemplos de todo este documento se usa el modelo Product:
namespace TagHelpersBuiltIn.Models
{
public class Product
{
public int Number { get; set; }
Ahora pasaremos a ver una relación de los atributos de la aplicación auxiliar de etiquetas parciales.
name
El atributo name es necesario. Señala el nombre o la ruta de acceso de la vista parcial que se va a representar.
Cuando se indica el nombre de una vista parcial, se inicia el proceso de detección de vista. Este proceso se omite
cuando se proporciona una ruta de acceso explícita.
En el siguiente marcado se usa una ruta de acceso explícita, lo que indica que _ProductPartial.cshtml debe
cargarse desde la carpeta Shared. Mediante el atributo for, se pasa un modelo a la vista parcial para el enlace.
<partial name="Shared/_ProductPartial.cshtml"
for="Product" />
for
El atributo for asigna una ModelExpression para que se evalúe según el modelo actual. ModelExpression deduce
la sintaxis de @Model. . Por ejemplo, se puede usar for="Product" en lugar de for="@Model.Product" . Este
comportamiento predeterminado de deducción queda invalidado si se usa el símbolo @ para definir una
expresión insertada. El atributo for no se puede usar con el atributo model.
El siguiente marcado carga _ProductPartial.cshtml:
<partial name="_ProductPartial"
for="Product" />
La vista parcial se enlaza a la propiedad Product del modelo de página asociado correspondiente:
using Microsoft.AspNetCore.Mvc.RazorPages;
using TagHelpersBuiltIn.Models;
namespace TagHelpersBuiltIn.Pages
{
public class ProductModel : PageModel
{
public Product Product { get; set; }
modelo
El atributo model asigna una instancia de modelo para pasarla a la vista parcial. El atributo model no se puede
usar con el atributo for.
En el siguiente código de marcado, se crea un objeto Product y se pasa al atributo model para el enlace:
<partial name="_ProductPartial"
model='new Product { Number = 1, Name = "Test product", Description = "This is a test" }' />
view-data
El atributo view-data asigna un ViewDataDictionary para pasarlo a la vista parcial. El siguiente marcado hace que
toda la colección ViewData esté accesible para la vista parcial:
@{
ViewData["IsNumberReadOnly"] = true;
}
<partial name="_ProductViewDataPartial"
for="Product"
view-data="@ViewData" />
En el código anterior, el valor de clave IsNumberReadOnly está establecido en true y se ha agregado a la colección
ViewData. Por tanto, ViewData["IsNumberReadOnly"] estará accesible dentro de la vista parcial siguiente:
@model TagHelpersBuiltIn.Models.Product
<div class="form-group">
<label asp-for="Number"></label>
@if ((bool)ViewData["IsNumberReadOnly"])
{
<input asp-for="Number" type="number" class="form-control" readonly />
}
else
{
<input asp-for="Number" type="number" class="form-control" />
}
</div>
<div class="form-group">
<label asp-for="Name"></label>
<input asp-for="Name" type="text" class="form-control" />
</div>
<div class="form-group">
<label asp-for="Description"></label>
<textarea asp-for="Description" rows="4" cols="50" class="form-control"></textarea>
</div>
Recursos adicionales
Vistas parciales
Datos con establecimiento flexible de tipos (ViewData, atributo ViewData y ViewBag)
Aplicaciones auxiliares de etiquetas en formularios de
ASP.NET Core
19/06/2018 • 29 minutes to read • Edit Online
El tiempo de ejecución MVC genera el valor de atributo action de los atributos de aplicación auxiliar de etiquetas
Form asp-controller y asp-action . La aplicación auxiliar de etiquetas Form genera también un token de
comprobación de solicitudes oculto que impide que se falsifiquen solicitudes entre sitios (cuando se usa con el
atributo [ValidateAntiForgeryToken] en el método de acción HTTP Post). Proteger un elemento HTML Form puro
de la falsificación de solicitudes entre sitios no es tarea fácil, y la aplicación auxiliar de etiquetas Form presta este
servicio.
Uso de una ruta con nombre
El atributo de aplicación auxiliar de etiquetas asp-route puede generar también el marcado del atributo HTML
action . Una aplicación con una ruta denominada register podría usar el siguiente marcado para la página de
registro:
Muchas de las vistas de la carpeta Views/Account (que se genera cuando se crea una aplicación web con Cuentas
de usuario individuales) contienen el atributo asp-route-returnurl:
NOTE
Con las plantillas integradas, returnUrl se rellena automáticamente solo cuando alguien intenta obtener acceso a un
recurso autorizado, pero no se ha autenticado o no tiene autorización. Si se intenta realizar un acceso no autorizado, el
middleware de seguridad redirige a la página de inicio de sesión con returnUrl configurado.
Type expected
'RegisterViewModel' does not contain a definition for 'Email' and no
extension method 'Email' accepting a first argument of type 'RegisterViewModel'
could be found (are you missing a using directive or an assembly reference?)
La aplicación auxiliar de etiquetas Input establece el atributo HTML type en función del tipo .NET. En la siguiente
tabla se enumeran algunos tipos .NET habituales y el tipo HTML generado correspondiente (no incluimos aquí
todos los tipos .NET).
Bool type=”checkbox”
String type=”text”
DateTime type=”datetime”
Byte type=”number”
En la siguiente tabla se muestran algunos atributos de anotación de datos comunes que la aplicación auxiliar de
etiquetas Input asignará a tipos de entrada concretos (no incluimos aquí todos los atributo de validación):
[EmailAddress] type=”email”
[Url] type=”url”
[HiddenInput] type=”hidden”
[Phone] type=”tel”
[DataType(DataType.Password)] type=”password”
[DataType(DataType.Date)] type=”date”
[DataType(DataType.Time)] type=”time”
Ejemplo:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
}
@model RegisterViewModel
Las anotaciones de datos que se aplican a las propiedades Email y Password generan metadatos en el modelo. La
aplicación auxiliar de etiquetas Input usa esos metadatos del modelo y genera atributos HTML5 data-val-* . Vea
Model Validation (Validación del modelo). Estos atributos describen los validadores que se van a adjuntar a los
campos de entrada, lo que proporciona HTML5 discreto y validación de jQuery. Los atributos discretos tienen el
formato data-val-rule="Error Message" , donde "rule" es el nombre de la regla de validación (como
data-val-required , data-val-email , data-val-maxlength , etc.). Si aparece un mensaje de error en el atributo, se
mostrará como el valor del atributo data-val-rule . También hay atributos con el formato
data-val-ruleName-argumentName="argumentValue" que aportan más información sobre la regla, por ejemplo,
data-val-maxlength-max="1024" .
Nombres de expresión
El valor del atributo asp-for es una ModelExpression y la parte de la derecha de una expresión lambda. Por tanto,
asp-for="Property1" se convierte en m => m.Property1 en el código generado, motivo por el que no es necesario
incluir el prefijo Model . Puede usar el carácter "@" para iniciar una expresión insertada y moverla antes de `m.`:
</span><span class="sxs-lookup">You can use the "@" character to start an inline expression and move before
the m. :
@{
var joe = "Joe";
}
<input asp-for="@joe" />
[DataType(DataType.Password)]
public string Password { get; set; }
@model RegisterAddressViewModel
El método de acción:
El siguiente código de Razor muestra cómo se tiene acceso a un elemento Color concreto:
@model Person
@{
var index = (int)ViewData["index"];
}
@model string
<label asp-for="@Model"></label>
<input asp-for="@Model" /> <br />
@model List<ToDoItem>
</table>
<button type="submit">Save</button>
</form>
La plantilla Views/Shared/EditorTemplates/ToDoItem.cshtml:
@model ToDoItem
<td>
<label asp-for="@Model.Name"></label>
@Html.DisplayFor(model => model.Name)
</td>
<td>
<input asp-for="@Model.IsDone" />
</td>
@*
This template replaces the following Razor which evaluates the indexer three times.
<td>
<label asp-for="@Model[i].Name"></label>
@Html.DisplayFor(model => model[i].Name)
</td>
<td>
<input asp-for="@Model[i].IsDone" />
</td>
*@
NOTE
Use siempre for (y no foreach ) para iterar por una lista. Evaluar un indizador en una expresión de LINQ puede ser
costoso, con lo cual esa posibilidad hay que reducirla al mínimo.
NOTE
El código de ejemplo comentado anterior muestra cómo reemplazaríamos la expresión lambda por el operador @ para tener
acceso a cada elemento ToDoItem de la lista.
Ejemplo:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class DescriptionViewModel
{
[MinLength(5)]
[MaxLength(1024)]
public string Description { get; set; }
}
}
@model DescriptionViewModel
Label Tag Helper proporciona las siguientes ventajas con respecto a un elemento HTML Label puro:
Obtendrá automáticamente el valor de la etiqueta descriptiva del atributo Display . El nombre para mostrar
que se busca puede cambiar con el tiempo y la combinación del atributo Display , y la aplicación auxiliar de
etiquetas Label aplicará el elemento Display en cualquier lugar donde se use.
El código fuente contiene menos marcado.
Permite establecer tipado fuerte con la propiedad de modelo.
Ejemplo:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class SimpleViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
}
}
@model SimpleViewModel
La aplicación auxiliar de etiquetas Label genera el valor de atributo for de "Email", que es el identificador asociado
al elemento <input> . Las aplicaciones auxiliares de etiqueta generan elementos id y for coherentes para que se
puedan asociar correctamente. El título de este ejemplo proviene del atributo Display . Si el modelo no contuviera
un atributo Display , el título sería el nombre de propiedad de la expresión.
Validation Message Tag Helper se usa con el atributo asp-validation-for en un elemento HTML span.
<span asp-validation-for="Email"></span>
<span class="field-validation-valid"
data-valmsg-for="Email"
data-valmsg-replace="true"></span>
Validation Message Tag Helper suele usarse por lo general después de una aplicación auxiliar de etiquetas Input
en la misma propiedad. Gracias a esto, se mostrarán todos los mensajes de error de validación cerca de la entrada
que produjo el error.
NOTE
Debe tener una vista con las referencias de script de JavaScript y de jQuery adecuadas para la validación del lado cliente. Para
más información, vea Introduction to model validation in ASP.NET Core MVC (Introducción a la validación de modelos en
ASP.NET Core MVC).
Cuando se produce un error de validación del lado servidor (por ejemplo, porque haya una validación del lado
servidor personalizada o porque la validación del lado cliente esté deshabilitada), MVC pone ese mensaje de error
como cuerpo del elemento <span> .
Validation Summary Tag Helper se usa para mostrar un resumen de los mensajes de validación. El valor de atributo
asp-validation-summary puede ser cualquiera de los siguientes:
ValidationSummary.ModelOnly Modelo
ValidationSummary.None Ninguna
Ejemplo
En el siguiente ejemplo, el modelo de datos se complementa con atributos DataAnnotation , lo que genera mensajes
de error de validación sobre el elemento <input> . Cuando se produce un error de validación, la aplicación auxiliar
de etiquetas de validación muestra el mensaje de error:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
}
@model RegisterViewModel
Ejemplo:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace FormsTagHelper.ViewModels
{
public class CountryViewModel
{
public string Country { get; set; }
El método Index inicializa CountryViewModel , establece el país seleccionado y lo pasa a la vista Index .
La vista Index :
@model CountryViewModel
NOTE
No se recomienda usar ViewBag o ViewData con la aplicación auxiliar de etiquetas Select. Un modelo de vista es más eficaz
a la hora de proporcionar metadatos MVC y suele ser menos problemático.
El valor de atributo asp-for es un caso especial y no necesita un prefijo Model , mientras que los otros atributos de
aplicación auxiliar de etiquetas sí (como asp-items ).
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}
@model CountryEnumViewModel
Puede complementar la lista de enumeradores con el atributo Display para obtener una interfaz de usuario más
completa:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}
Agrupamiento de opciones
El elemento HTML <optgroup> se genera cuando el modelo de vista contiene uno o varios objetos
SelectListGroup .
CountryViewModelGroup agrupa los elementos SelectListItem en los grupos "North America" y "Europe":
public class CountryViewModelGroup
{
public CountryViewModelGroup()
{
var NorthAmericaGroup = new SelectListGroup { Name = "North America" };
var EuropeGroup = new SelectListGroup { Name = "Europe" };
Selección múltiple
La aplicación auxiliar de etiquetas Select generará automáticamente el atributo multiple = "multiple" si la propiedad
especificada en el atributo asp-for es IEnumerable . Por ejemplo, si tenemos el siguiente modelo:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace FormsTagHelper.ViewModels
{
public class CountryViewModelIEnumerable
{
public IEnumerable<string> CountryCodes { get; set; }
Sin selección
Si ve que usa la opción "sin especificar" en varias páginas, puede crear una plantilla para no tener que repetir el
código HTML:
@model CountryViewModel
La plantilla Views/Shared/EditorTemplates/CountryViewModel.cshtml:
@model CountryViewModel
La posibilidad de agregar elementos HTML <option> no se limita exclusivamente a los casos en los que no se
seleccionada nada. Por ejemplo, el método de acción y vista siguientes generarán un código HTML similar al
código anterior:
Se seleccionará el elemento <option> correcto (que contenga el atributo selected="selected" ) en función del valor
real de Country .
Recursos adicionales
Aplicaciones auxiliares de etiquetas
HTML Form element (Elemento HTML Form)
Request Verification Token (Token de comprobación de solicitudes)
Enlace de modelos
Introduction to model validation in ASP.NET Core MVC (Introducción a la validación de modelos en ASP.NET
Core MVC )
IAttributeAdapter Interface (Interfaz IAttributeAdapter)
Fragmentos de código de este documento
Aplicaciones auxiliares de etiquetas en formularios de
ASP.NET Core
19/06/2018 • 29 minutes to read • Edit Online
El tiempo de ejecución MVC genera el valor de atributo action de los atributos de aplicación auxiliar de etiquetas
Form asp-controller y asp-action . La aplicación auxiliar de etiquetas Form genera también un token de
comprobación de solicitudes oculto que impide que se falsifiquen solicitudes entre sitios (cuando se usa con el
atributo [ValidateAntiForgeryToken] en el método de acción HTTP Post). Proteger un elemento HTML Form puro
de la falsificación de solicitudes entre sitios no es tarea fácil, y la aplicación auxiliar de etiquetas Form presta este
servicio.
Uso de una ruta con nombre
El atributo de aplicación auxiliar de etiquetas asp-route puede generar también el marcado del atributo HTML
action . Una aplicación con una ruta denominada register podría usar el siguiente marcado para la página de
registro:
Muchas de las vistas de la carpeta Views/Account (que se genera cuando se crea una aplicación web con Cuentas
de usuario individuales) contienen el atributo asp-route-returnurl:
NOTE
Con las plantillas integradas, returnUrl se rellena automáticamente solo cuando alguien intenta obtener acceso a un
recurso autorizado, pero no se ha autenticado o no tiene autorización. Si se intenta realizar un acceso no autorizado, el
middleware de seguridad redirige a la página de inicio de sesión con returnUrl configurado.
Type expected
'RegisterViewModel' does not contain a definition for 'Email' and no
extension method 'Email' accepting a first argument of type 'RegisterViewModel'
could be found (are you missing a using directive or an assembly reference?)
La aplicación auxiliar de etiquetas Input establece el atributo HTML type en función del tipo .NET. En la siguiente
tabla se enumeran algunos tipos .NET habituales y el tipo HTML generado correspondiente (no incluimos aquí
todos los tipos .NET).
Bool type=”checkbox”
String type=”text”
DateTime type=”datetime”
Byte type=”number”
En la siguiente tabla se muestran algunos atributos de anotación de datos comunes que la aplicación auxiliar de
etiquetas Input asignará a tipos de entrada concretos (no incluimos aquí todos los atributo de validación):
[EmailAddress] type=”email”
[Url] type=”url”
[HiddenInput] type=”hidden”
[Phone] type=”tel”
[DataType(DataType.Password)] type=”password”
[DataType(DataType.Date)] type=”date”
[DataType(DataType.Time)] type=”time”
Ejemplo:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
}
@model RegisterViewModel
Las anotaciones de datos que se aplican a las propiedades Email y Password generan metadatos en el modelo. La
aplicación auxiliar de etiquetas Input usa esos metadatos del modelo y genera atributos HTML5 data-val-* . Vea
Model Validation (Validación del modelo). Estos atributos describen los validadores que se van a adjuntar a los
campos de entrada, lo que proporciona HTML5 discreto y validación de jQuery. Los atributos discretos tienen el
formato data-val-rule="Error Message" , donde "rule" es el nombre de la regla de validación (como
data-val-required , data-val-email , data-val-maxlength , etc.). Si aparece un mensaje de error en el atributo, se
mostrará como el valor del atributo data-val-rule . También hay atributos con el formato
data-val-ruleName-argumentName="argumentValue" que aportan más información sobre la regla, por ejemplo,
data-val-maxlength-max="1024" .
Nombres de expresión
El valor del atributo asp-for es una ModelExpression y la parte de la derecha de una expresión lambda. Por tanto,
asp-for="Property1" se convierte en m => m.Property1 en el código generado, motivo por el que no es necesario
incluir el prefijo Model . Puede usar el carácter "@" para iniciar una expresión insertada y moverla antes de `m.`:
</span><span class="sxs-lookup">You can use the "@" character to start an inline expression and move before
the m. :
@{
var joe = "Joe";
}
<input asp-for="@joe" />
[DataType(DataType.Password)]
public string Password { get; set; }
@model RegisterAddressViewModel
El método de acción:
El siguiente código de Razor muestra cómo se tiene acceso a un elemento Color concreto:
@model Person
@{
var index = (int)ViewData["index"];
}
@model string
<label asp-for="@Model"></label>
<input asp-for="@Model" /> <br />
@model List<ToDoItem>
</table>
<button type="submit">Save</button>
</form>
La plantilla Views/Shared/EditorTemplates/ToDoItem.cshtml:
@model ToDoItem
<td>
<label asp-for="@Model.Name"></label>
@Html.DisplayFor(model => model.Name)
</td>
<td>
<input asp-for="@Model.IsDone" />
</td>
@*
This template replaces the following Razor which evaluates the indexer three times.
<td>
<label asp-for="@Model[i].Name"></label>
@Html.DisplayFor(model => model[i].Name)
</td>
<td>
<input asp-for="@Model[i].IsDone" />
</td>
*@
NOTE
Use siempre for (y no foreach ) para iterar por una lista. Evaluar un indizador en una expresión de LINQ puede ser
costoso, con lo cual esa posibilidad hay que reducirla al mínimo.
NOTE
El código de ejemplo comentado anterior muestra cómo reemplazaríamos la expresión lambda por el operador @ para tener
acceso a cada elemento ToDoItem de la lista.
Ejemplo:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class DescriptionViewModel
{
[MinLength(5)]
[MaxLength(1024)]
public string Description { get; set; }
}
}
@model DescriptionViewModel
Label Tag Helper proporciona las siguientes ventajas con respecto a un elemento HTML Label puro:
Obtendrá automáticamente el valor de la etiqueta descriptiva del atributo Display . El nombre para mostrar
que se busca puede cambiar con el tiempo y la combinación del atributo Display , y la aplicación auxiliar de
etiquetas Label aplicará el elemento Display en cualquier lugar donde se use.
El código fuente contiene menos marcado.
Permite establecer tipado fuerte con la propiedad de modelo.
Ejemplo:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class SimpleViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
}
}
@model SimpleViewModel
La aplicación auxiliar de etiquetas Label genera el valor de atributo for de "Email", que es el identificador asociado
al elemento <input> . Las aplicaciones auxiliares de etiqueta generan elementos id y for coherentes para que se
puedan asociar correctamente. El título de este ejemplo proviene del atributo Display . Si el modelo no contuviera
un atributo Display , el título sería el nombre de propiedad de la expresión.
Validation Message Tag Helper se usa con el atributo asp-validation-for en un elemento HTML span.
<span asp-validation-for="Email"></span>
<span class="field-validation-valid"
data-valmsg-for="Email"
data-valmsg-replace="true"></span>
Validation Message Tag Helper suele usarse por lo general después de una aplicación auxiliar de etiquetas Input
en la misma propiedad. Gracias a esto, se mostrarán todos los mensajes de error de validación cerca de la entrada
que produjo el error.
NOTE
Debe tener una vista con las referencias de script de JavaScript y de jQuery adecuadas para la validación del lado cliente. Para
más información, vea Introduction to model validation in ASP.NET Core MVC (Introducción a la validación de modelos en
ASP.NET Core MVC).
Cuando se produce un error de validación del lado servidor (por ejemplo, porque haya una validación del lado
servidor personalizada o porque la validación del lado cliente esté deshabilitada), MVC pone ese mensaje de error
como cuerpo del elemento <span> .
Validation Summary Tag Helper se usa para mostrar un resumen de los mensajes de validación. El valor de atributo
asp-validation-summary puede ser cualquiera de los siguientes:
ValidationSummary.ModelOnly Modelo
ValidationSummary.None Ninguna
Ejemplo
En el siguiente ejemplo, el modelo de datos se complementa con atributos DataAnnotation , lo que genera mensajes
de error de validación sobre el elemento <input> . Cuando se produce un error de validación, la aplicación auxiliar
de etiquetas de validación muestra el mensaje de error:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
}
@model RegisterViewModel
Ejemplo:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace FormsTagHelper.ViewModels
{
public class CountryViewModel
{
public string Country { get; set; }
El método Index inicializa CountryViewModel , establece el país seleccionado y lo pasa a la vista Index .
La vista Index :
@model CountryViewModel
NOTE
No se recomienda usar ViewBag o ViewData con la aplicación auxiliar de etiquetas Select. Un modelo de vista es más eficaz
a la hora de proporcionar metadatos MVC y suele ser menos problemático.
El valor de atributo asp-for es un caso especial y no necesita un prefijo Model , mientras que los otros atributos de
aplicación auxiliar de etiquetas sí (como asp-items ).
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}
@model CountryEnumViewModel
Puede complementar la lista de enumeradores con el atributo Display para obtener una interfaz de usuario más
completa:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}
Agrupamiento de opciones
El elemento HTML <optgroup> se genera cuando el modelo de vista contiene uno o varios objetos
SelectListGroup .
CountryViewModelGroup agrupa los elementos SelectListItem en los grupos "North America" y "Europe":
public class CountryViewModelGroup
{
public CountryViewModelGroup()
{
var NorthAmericaGroup = new SelectListGroup { Name = "North America" };
var EuropeGroup = new SelectListGroup { Name = "Europe" };
Selección múltiple
La aplicación auxiliar de etiquetas Select generará automáticamente el atributo multiple = "multiple" si la propiedad
especificada en el atributo asp-for es IEnumerable . Por ejemplo, si tenemos el siguiente modelo:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace FormsTagHelper.ViewModels
{
public class CountryViewModelIEnumerable
{
public IEnumerable<string> CountryCodes { get; set; }
Sin selección
Si ve que usa la opción "sin especificar" en varias páginas, puede crear una plantilla para no tener que repetir el
código HTML:
@model CountryViewModel
La plantilla Views/Shared/EditorTemplates/CountryViewModel.cshtml:
@model CountryViewModel
La posibilidad de agregar elementos HTML <option> no se limita exclusivamente a los casos en los que no se
seleccionada nada. Por ejemplo, el método de acción y vista siguientes generarán un código HTML similar al
código anterior:
Se seleccionará el elemento <option> correcto (que contenga el atributo selected="selected" ) en función del valor
real de Country .
Recursos adicionales
Aplicaciones auxiliares de etiquetas
HTML Form element (Elemento HTML Form)
Request Verification Token (Token de comprobación de solicitudes)
Enlace de modelos
Introduction to model validation in ASP.NET Core MVC (Introducción a la validación de modelos en ASP.NET
Core MVC )
IAttributeAdapter Interface (Interfaz IAttributeAdapter)
Fragmentos de código de este documento
Aplicaciones auxiliares de etiquetas en formularios de
ASP.NET Core
19/06/2018 • 29 minutes to read • Edit Online
El tiempo de ejecución MVC genera el valor de atributo action de los atributos de aplicación auxiliar de etiquetas
Form asp-controller y asp-action . La aplicación auxiliar de etiquetas Form genera también un token de
comprobación de solicitudes oculto que impide que se falsifiquen solicitudes entre sitios (cuando se usa con el
atributo [ValidateAntiForgeryToken] en el método de acción HTTP Post). Proteger un elemento HTML Form puro
de la falsificación de solicitudes entre sitios no es tarea fácil, y la aplicación auxiliar de etiquetas Form presta este
servicio.
Uso de una ruta con nombre
El atributo de aplicación auxiliar de etiquetas asp-route puede generar también el marcado del atributo HTML
action . Una aplicación con una ruta denominada register podría usar el siguiente marcado para la página de
registro:
Muchas de las vistas de la carpeta Views/Account (que se genera cuando se crea una aplicación web con Cuentas
de usuario individuales) contienen el atributo asp-route-returnurl:
NOTE
Con las plantillas integradas, returnUrl se rellena automáticamente solo cuando alguien intenta obtener acceso a un
recurso autorizado, pero no se ha autenticado o no tiene autorización. Si se intenta realizar un acceso no autorizado, el
middleware de seguridad redirige a la página de inicio de sesión con returnUrl configurado.
Type expected
'RegisterViewModel' does not contain a definition for 'Email' and no
extension method 'Email' accepting a first argument of type 'RegisterViewModel'
could be found (are you missing a using directive or an assembly reference?)
La aplicación auxiliar de etiquetas Input establece el atributo HTML type en función del tipo .NET. En la siguiente
tabla se enumeran algunos tipos .NET habituales y el tipo HTML generado correspondiente (no incluimos aquí
todos los tipos .NET).
Bool type=”checkbox”
String type=”text”
DateTime type=”datetime”
Byte type=”number”
En la siguiente tabla se muestran algunos atributos de anotación de datos comunes que la aplicación auxiliar de
etiquetas Input asignará a tipos de entrada concretos (no incluimos aquí todos los atributo de validación):
[EmailAddress] type=”email”
[Url] type=”url”
[HiddenInput] type=”hidden”
[Phone] type=”tel”
[DataType(DataType.Password)] type=”password”
[DataType(DataType.Date)] type=”date”
[DataType(DataType.Time)] type=”time”
Ejemplo:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
}
@model RegisterViewModel
Las anotaciones de datos que se aplican a las propiedades Email y Password generan metadatos en el modelo. La
aplicación auxiliar de etiquetas Input usa esos metadatos del modelo y genera atributos HTML5 data-val-* . Vea
Model Validation (Validación del modelo). Estos atributos describen los validadores que se van a adjuntar a los
campos de entrada, lo que proporciona HTML5 discreto y validación de jQuery. Los atributos discretos tienen el
formato data-val-rule="Error Message" , donde "rule" es el nombre de la regla de validación (como
data-val-required , data-val-email , data-val-maxlength , etc.). Si aparece un mensaje de error en el atributo, se
mostrará como el valor del atributo data-val-rule . También hay atributos con el formato
data-val-ruleName-argumentName="argumentValue" que aportan más información sobre la regla, por ejemplo,
data-val-maxlength-max="1024" .
Nombres de expresión
El valor del atributo asp-for es una ModelExpression y la parte de la derecha de una expresión lambda. Por tanto,
asp-for="Property1" se convierte en m => m.Property1 en el código generado, motivo por el que no es necesario
incluir el prefijo Model . Puede usar el carácter "@" para iniciar una expresión insertada y moverla antes de `m.`:
</span><span class="sxs-lookup">You can use the "@" character to start an inline expression and move before
the m. :
@{
var joe = "Joe";
}
<input asp-for="@joe" />
[DataType(DataType.Password)]
public string Password { get; set; }
@model RegisterAddressViewModel
El método de acción:
El siguiente código de Razor muestra cómo se tiene acceso a un elemento Color concreto:
@model Person
@{
var index = (int)ViewData["index"];
}
@model string
<label asp-for="@Model"></label>
<input asp-for="@Model" /> <br />
@model List<ToDoItem>
</table>
<button type="submit">Save</button>
</form>
La plantilla Views/Shared/EditorTemplates/ToDoItem.cshtml:
@model ToDoItem
<td>
<label asp-for="@Model.Name"></label>
@Html.DisplayFor(model => model.Name)
</td>
<td>
<input asp-for="@Model.IsDone" />
</td>
@*
This template replaces the following Razor which evaluates the indexer three times.
<td>
<label asp-for="@Model[i].Name"></label>
@Html.DisplayFor(model => model[i].Name)
</td>
<td>
<input asp-for="@Model[i].IsDone" />
</td>
*@
NOTE
Use siempre for (y no foreach ) para iterar por una lista. Evaluar un indizador en una expresión de LINQ puede ser
costoso, con lo cual esa posibilidad hay que reducirla al mínimo.
NOTE
El código de ejemplo comentado anterior muestra cómo reemplazaríamos la expresión lambda por el operador @ para tener
acceso a cada elemento ToDoItem de la lista.
Ejemplo:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class DescriptionViewModel
{
[MinLength(5)]
[MaxLength(1024)]
public string Description { get; set; }
}
}
@model DescriptionViewModel
Label Tag Helper proporciona las siguientes ventajas con respecto a un elemento HTML Label puro:
Obtendrá automáticamente el valor de la etiqueta descriptiva del atributo Display . El nombre para mostrar
que se busca puede cambiar con el tiempo y la combinación del atributo Display , y la aplicación auxiliar de
etiquetas Label aplicará el elemento Display en cualquier lugar donde se use.
El código fuente contiene menos marcado.
Permite establecer tipado fuerte con la propiedad de modelo.
Ejemplo:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class SimpleViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
}
}
@model SimpleViewModel
La aplicación auxiliar de etiquetas Label genera el valor de atributo for de "Email", que es el identificador asociado
al elemento <input> . Las aplicaciones auxiliares de etiqueta generan elementos id y for coherentes para que se
puedan asociar correctamente. El título de este ejemplo proviene del atributo Display . Si el modelo no contuviera
un atributo Display , el título sería el nombre de propiedad de la expresión.
Validation Message Tag Helper se usa con el atributo asp-validation-for en un elemento HTML span.
<span asp-validation-for="Email"></span>
<span class="field-validation-valid"
data-valmsg-for="Email"
data-valmsg-replace="true"></span>
Validation Message Tag Helper suele usarse por lo general después de una aplicación auxiliar de etiquetas Input
en la misma propiedad. Gracias a esto, se mostrarán todos los mensajes de error de validación cerca de la entrada
que produjo el error.
NOTE
Debe tener una vista con las referencias de script de JavaScript y de jQuery adecuadas para la validación del lado cliente. Para
más información, vea Introduction to model validation in ASP.NET Core MVC (Introducción a la validación de modelos en
ASP.NET Core MVC).
Cuando se produce un error de validación del lado servidor (por ejemplo, porque haya una validación del lado
servidor personalizada o porque la validación del lado cliente esté deshabilitada), MVC pone ese mensaje de error
como cuerpo del elemento <span> .
Validation Summary Tag Helper se usa para mostrar un resumen de los mensajes de validación. El valor de atributo
asp-validation-summary puede ser cualquiera de los siguientes:
ValidationSummary.ModelOnly Modelo
ValidationSummary.None Ninguna
Ejemplo
En el siguiente ejemplo, el modelo de datos se complementa con atributos DataAnnotation , lo que genera mensajes
de error de validación sobre el elemento <input> . Cuando se produce un error de validación, la aplicación auxiliar
de etiquetas de validación muestra el mensaje de error:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
}
@model RegisterViewModel
Ejemplo:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace FormsTagHelper.ViewModels
{
public class CountryViewModel
{
public string Country { get; set; }
El método Index inicializa CountryViewModel , establece el país seleccionado y lo pasa a la vista Index .
La vista Index :
@model CountryViewModel
NOTE
No se recomienda usar ViewBag o ViewData con la aplicación auxiliar de etiquetas Select. Un modelo de vista es más eficaz
a la hora de proporcionar metadatos MVC y suele ser menos problemático.
El valor de atributo asp-for es un caso especial y no necesita un prefijo Model , mientras que los otros atributos de
aplicación auxiliar de etiquetas sí (como asp-items ).
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}
@model CountryEnumViewModel
Puede complementar la lista de enumeradores con el atributo Display para obtener una interfaz de usuario más
completa:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}
Agrupamiento de opciones
El elemento HTML <optgroup> se genera cuando el modelo de vista contiene uno o varios objetos
SelectListGroup .
CountryViewModelGroup agrupa los elementos SelectListItem en los grupos "North America" y "Europe":
public class CountryViewModelGroup
{
public CountryViewModelGroup()
{
var NorthAmericaGroup = new SelectListGroup { Name = "North America" };
var EuropeGroup = new SelectListGroup { Name = "Europe" };
Selección múltiple
La aplicación auxiliar de etiquetas Select generará automáticamente el atributo multiple = "multiple" si la propiedad
especificada en el atributo asp-for es IEnumerable . Por ejemplo, si tenemos el siguiente modelo:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace FormsTagHelper.ViewModels
{
public class CountryViewModelIEnumerable
{
public IEnumerable<string> CountryCodes { get; set; }
Sin selección
Si ve que usa la opción "sin especificar" en varias páginas, puede crear una plantilla para no tener que repetir el
código HTML:
@model CountryViewModel
La plantilla Views/Shared/EditorTemplates/CountryViewModel.cshtml:
@model CountryViewModel
La posibilidad de agregar elementos HTML <option> no se limita exclusivamente a los casos en los que no se
seleccionada nada. Por ejemplo, el método de acción y vista siguientes generarán un código HTML similar al
código anterior:
Se seleccionará el elemento <option> correcto (que contenga el atributo selected="selected" ) en función del valor
real de Country .
Recursos adicionales
Aplicaciones auxiliares de etiquetas
HTML Form element (Elemento HTML Form)
Request Verification Token (Token de comprobación de solicitudes)
Enlace de modelos
Introduction to model validation in ASP.NET Core MVC (Introducción a la validación de modelos en ASP.NET
Core MVC )
IAttributeAdapter Interface (Interfaz IAttributeAdapter)
Fragmentos de código de este documento
Aplicaciones auxiliares de etiquetas en
formularios de ASP.NET Core
19/06/2018 • 29 minutes to read • Edit Online
Muchas de las vistas de la carpeta Views/Account (que se genera cuando se crea una
aplicación web con Cuentas de usuario individuales) contienen el atributo asp-route-
returnurl:
NOTE
Con las plantillas integradas, returnUrl se rellena automáticamente solo cuando alguien
intenta obtener acceso a un recurso autorizado, pero no se ha autenticado o no tiene
autorización. Si se intenta realizar un acceso no autorizado, el middleware de seguridad
redirige a la página de inicio de sesión con returnUrl configurado.
Type expected
'RegisterViewModel' does not contain a definition for 'Email' and no
extension method 'Email' accepting a first argument of type 'RegisterViewModel'
could be found (are you missing a using directive or an assembly reference?)
Bool type=”checkbox”
String type=”text”
DateTime type=”datetime”
Byte type=”number”
[EmailAddress] type=”email”
ATRIBUTO TIPO DE ENTRADA
[Url] type=”url”
[HiddenInput] type=”hidden”
[Phone] type=”tel”
[DataType(DataType.Password)] type=”password”
[DataType(DataType.Date)] type=”date”
[DataType(DataType.Time)] type=”time”
Ejemplo:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
}
@model RegisterViewModel
HtmlAttributes
@Html.Editor() y @Html.EditorFor()usan una entrada ViewDataDictionary especial
denominada htmlAttributes al ejecutar sus plantillas predeterminadas. Si lo desea,
este comportamiento se puede enriquecer con parámetros additionalViewData . En la
clave "htmlAttributes" se distingue entre mayúsculas y minúsculas. La clave
"htmlAttributes" se controla de forma similar al objeto htmlAttributes pasado a
aplicaciones auxiliares de etiqueta Input como @Html.TextBox() .
Nombres de expresión
El valor del atributo asp-for es una ModelExpression y la parte de la derecha de una
expresión lambda. Por tanto, asp-for="Property1" se convierte en m => m.Property1
en el código generado, motivo por el que no es necesario incluir el prefijo Model .
Puede usar el carácter "@" para iniciar una expresión insertada y moverla antes de
`m.`:</span><span class="sxs-lookup">You can use the "@" character to start an
inline expression and move before the m. :
@{
var joe = "Joe";
}
<input asp-for="@joe" />
[DataType(DataType.Password)]
public string Password { get; set; }
@model RegisterAddressViewModel
El método de acción:
public IActionResult Edit(int id, int colorIndex)
{
ViewData["Index"] = colorIndex;
return View(GetPerson(id));
}
@model Person
@{
var index = (int)ViewData["index"];
}
La plantilla Views/Shared/EditorTemplates/String.cshtml:
@model string
<label asp-for="@Model"></label>
<input asp-for="@Model" /> <br />
@model List<ToDoItem>
</table>
<button type="submit">Save</button>
</form>
La plantilla Views/Shared/EditorTemplates/ToDoItem.cshtml:
@model ToDoItem
<td>
<label asp-for="@Model.Name"></label>
@Html.DisplayFor(model => model.Name)
</td>
<td>
<input asp-for="@Model.IsDone" />
</td>
@*
This template replaces the following Razor which evaluates the indexer three
times.
<td>
<label asp-for="@Model[i].Name"></label>
@Html.DisplayFor(model => model[i].Name)
</td>
<td>
<input asp-for="@Model[i].IsDone" />
</td>
*@
NOTE
Use siempre for (y no foreach ) para iterar por una lista. Evaluar un indizador en una
expresión de LINQ puede ser costoso, con lo cual esa posibilidad hay que reducirla al
mínimo.
NOTE
El código de ejemplo comentado anterior muestra cómo reemplazaríamos la expresión
lambda por el operador @ para tener acceso a cada elemento ToDoItem de la lista.
Ejemplo:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class DescriptionViewModel
{
[MinLength(5)]
[MaxLength(1024)]
public string Description { get; set; }
}
}
@model DescriptionViewModel
Label Tag Helper proporciona las siguientes ventajas con respecto a un elemento
HTML Label puro:
Obtendrá automáticamente el valor de la etiqueta descriptiva del atributo
Display . El nombre para mostrar que se busca puede cambiar con el tiempo y
la combinación del atributo Display , y la aplicación auxiliar de etiquetas Label
aplicará el elemento Display en cualquier lugar donde se use.
El código fuente contiene menos marcado.
Permite establecer tipado fuerte con la propiedad de modelo.
Ejemplo:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class SimpleViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
}
}
@model SimpleViewModel
<span asp-validation-for="Email"></span>
<span class="field-validation-valid"
data-valmsg-for="Email"
data-valmsg-replace="true"></span>
Validation Message Tag Helper suele usarse por lo general después de una aplicación
auxiliar de etiquetas Input en la misma propiedad. Gracias a esto, se mostrarán
todos los mensajes de error de validación cerca de la entrada que produjo el error.
NOTE
Debe tener una vista con las referencias de script de JavaScript y de jQuery adecuadas para
la validación del lado cliente. Para más información, vea Introduction to model validation in
ASP.NET Core MVC (Introducción a la validación de modelos en ASP.NET Core MVC).
Cuando se produce un error de validación del lado servidor (por ejemplo, porque
haya una validación del lado servidor personalizada o porque la validación del lado
cliente esté deshabilitada), MVC pone ese mensaje de error como cuerpo del
elemento <span> .
Validation Summary Tag Helper se usa para mostrar un resumen de los mensajes de
validación. El valor de atributo asp-validation-summary puede ser cualquiera de los
siguientes:
ValidationSummary.ModelOnly Modelo
ValidationSummary.None Ninguna
Ejemplo
En el siguiente ejemplo, el modelo de datos se complementa con atributos
DataAnnotation , lo que genera mensajes de error de validación sobre el elemento
<input> . Cuando se produce un error de validación, la aplicación auxiliar de etiquetas
de validación muestra el mensaje de error:
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email Address")]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
}
@model RegisterViewModel
Ejemplo:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace FormsTagHelper.ViewModels
{
public class CountryViewModel
{
public string Country { get; set; }
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index(CountryViewModel model)
{
if (ModelState.IsValid)
{
var msg = model.Country + " selected";
return RedirectToAction("IndexSuccess", new { message = msg});
}
La vista Index :
@model CountryViewModel
NOTE
No se recomienda usar ViewBag o ViewData con la aplicación auxiliar de etiquetas Select.
Un modelo de vista es más eficaz a la hora de proporcionar metadatos MVC y suele ser
menos problemático.
namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}
@model CountryEnumViewModel
using System.ComponentModel.DataAnnotations;
namespace FormsTagHelper.ViewModels
{
public enum CountryEnum
{
[Display(Name = "United Mexican States")]
Mexico,
[Display(Name = "United States of America")]
USA,
Canada,
France,
Germany,
Spain
}
}
Agrupamiento de opciones
El elemento HTML <optgroup> se genera cuando el modelo de vista contiene uno o
varios objetos SelectListGroup .
CountryViewModelGroup agrupa los elementos SelectListItem en los grupos "North
America" y "Europe":
public class CountryViewModelGroup
{
public CountryViewModelGroup()
{
var NorthAmericaGroup = new SelectListGroup { Name = "North America" };
var EuropeGroup = new SelectListGroup { Name = "Europe" };
Selección múltiple
La aplicación auxiliar de etiquetas Select generará automáticamente el atributo
multiple = "multiple" si la propiedad especificada en el atributo asp-for es
IEnumerable . Por ejemplo, si tenemos el siguiente modelo:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace FormsTagHelper.ViewModels
{
public class CountryViewModelIEnumerable
{
public IEnumerable<string> CountryCodes { get; set; }
@model CountryViewModelIEnumerable
Sin selección
Si ve que usa la opción "sin especificar" en varias páginas, puede crear una plantilla
para no tener que repetir el código HTML:
@model CountryViewModel
La plantilla Views/Shared/EditorTemplates/CountryViewModel.cshtml:
@model CountryViewModel
@model CountryViewModel
Recursos adicionales
Aplicaciones auxiliares de etiquetas
HTML Form element (Elemento HTML Form)
Request Verification Token (Token de comprobación de solicitudes)
Enlace de modelos
Introduction to model validation in ASP.NET Core MVC (Introducción a la
validación de modelos en ASP.NET Core MVC )
IAttributeAdapter Interface (Interfaz IAttributeAdapter)
Fragmentos de código de este documento
Vistas parciales en ASP.NET Core
25/06/2018 • 9 minutes to read • Edit Online
@await Html.PartialAsync("AuthorPartial")
Puede representar una vista parcial con RenderPartialAsync . Este método no devuelve ningún resultado;
transmite por secuencias la salida representada directamente a la respuesta. Como no devuelve ningún
resultado, debe llamarse desde un bloque de código de Razor:
@{
await Html.RenderPartialAsync("AuthorPartial");
}
Dado que transmite por secuencias el resultado directamente, RenderPartialAsync puede funcionar mejor en
algunos escenarios, pero se recomienda usar PartialAsync .
Aunque hay equivalentes sincrónicos de Html.PartialAsync ( Html.Partial ) y de Html.RenderPartialAsync (
Html.RenderPartial ), su uso no es aconsejable, ya que hay escenarios donde producen interbloqueos. Los
métodos sincrónicos dejarán de estar disponibles en futuras versiones.
NOTE
Si las vistas necesitan ejecutar código, el patrón recomendado es usar un componente de vista en lugar de una vista
parcial.
Puede tener diferentes vistas parciales con el mismo nombre en distintas carpetas de vistas. Cuando se hace
referencia a las vistas por su nombre (sin extensión de archivo), las vistas de cada carpeta usarán la vista
parcial en la misma carpeta que ellas. También puede especificar una vista parcial predeterminada que vaya a
usar, colocándola en la carpeta Shared. Cualquier vista que no tenga su propia versión de la vista parcial usará
la vista parcial compartida. Puede tener una vista parcial predeterminada (en Shared), que se haya
reemplazado por una vista parcial con el mismo nombre y en la misma carpeta que la vista principal.
Las vistas parciales se pueden encadenar. Es decir, una vista parcial puede llamar a otra vista parcial (siempre
que no creen un bucle). Dentro de cada vista o vista parcial, las rutas de acceso relativas son siempre relativas
con respecto a esa vista, no con respecto a la vista principal o de raíz.
NOTE
Si declara un section de Razor en una vista parcial, no estará visible para sus vistas principales, sino que se limitará a la
vista parcial.
También puede pasar un modelo a una vista parcial. Puede tratarse del modelo de vista de la página o un
objeto personalizado. Puede pasar un modelo a PartialAsync o a RenderPartialAsync :
Puede pasar una instancia de ViewDataDictionary y un modelo de vista a una vista parcial:
El marcado siguiente muestra la vista Views/Articles/Read.cshtml que contiene dos vistas parciales. La segunda
vista parcial se pasa a un modelo y ViewData a la vista parcial. Puede pasar un nuevo diccionario de ViewData
a la vez que conserva el ViewData existente si usa la sobrecarga del constructor de ViewDataDictionary
resaltado aquí abajo:
@using Microsoft.AspNetCore.Mvc.ViewFeatures
@using PartialViewsSample.ViewModels
@model Article
<h2>@Model.Title</h2>
@*Pass the authors name to Views\Shared\AuthorPartial.cshtml*@
@await Html.PartialAsync("AuthorPartial", Model.AuthorName)
@Model.PublicationDate
@*Loop over the Sections and pass in a section and additional ViewData
to the strongly typed Views\Articles\ArticleSection.cshtml partial view.*@
@{ var index = 0;
@foreach (var section in Model.Sections)
{
@await Html.PartialAsync("ArticleSection", section,
new ViewDataDictionary(this.ViewData) { { "index", index } })
index++;
}
}
Views/Shared/AuthorPartial:
@model string
<div>
<h3>@Model</h3>
This partial view came from /Views/Shared/AuthorPartial.cshtml.<br />
</div>
En tiempo de ejecución, las vistas parciales se representan en la vista principal, que a su vez se representa
dentro de _Layout.cshtml compartido.
Inserción de dependencias en vistas de ASP.NET
Core
14/05/2018 • 7 minutes to read • Edit Online
Ejemplo sencillo
Puede insertar un servicio en una vista mediante la directiva @inject . Puede pensar que @inject es como si
agregara una propiedad a la vista y rellenara la propiedad mediante DI.
La sintaxis de @inject : @inject <type> <name>
Esta vista muestra una lista de instancias ToDoItem , junto con un resumen de estadísticas generales. El resumen
se rellena a partir de StatisticsService insertado. Este servicio está registrado para la inserción de
dependencias en ConfigureServices en Startup.cs:
services.AddTransient<IToDoItemRepository, ToDoItemRepository>();
services.AddTransient<StatisticsService>();
services.AddTransient<ProfileOptionsService>();
StatisticsService realiza algunos cálculos en el conjunto de instancias de ToDoItem , al que se accede a través
de un repositorio:
using System.Linq;
using ViewInjectSample.Interfaces;
namespace ViewInjectSample.Model.Services
{
public class StatisticsService
{
private readonly IToDoItemRepository _toDoItemRepository;
El repositorio de ejemplo usa una colección en memoria. La implementación que se muestra arriba (que
funciona en todos los datos en memoria) no se recomienda para conjuntos de datos grandes, con acceso de
forma remota.
El ejemplo muestra los datos del modelo enlazado a la vista y del servicio que se inserta en la vista:
using Microsoft.AspNetCore.Mvc;
using ViewInjectSample.Model;
namespace ViewInjectSample.Controllers
{
public class ProfileController : Controller
{
[Route("Profile")]
public IActionResult Index()
{
// TODO: look up profile based on logged-in user
var profile = new Profile()
{
Name = "Steve",
FavColor = "Blue",
Gender = "Male",
State = new State("Ohio","OH")
};
return View(profile);
}
}
}
El formulario HTML que se utilizó para actualizar estas preferencias incluye listas desplegables para tres de las
propiedades:
ProfileOptionsService es un servicio de nivel de interfaz de usuario diseñado para proporcionar solo los datos
necesarios para este formulario:
using System.Collections.Generic;
namespace ViewInjectSample.Model.Services
{
public class ProfileOptionsService
{
public List<string> ListGenders()
{
// keeping this simple
return new List<string>() {"Female", "Male"};
}
Reemplazar servicios
Además de insertar nuevos servicios, esta técnica también puede usarse para reemplazar servicios previamente
insertados en una página. En la imagen de abajo se muestran todos los campos disponibles en la página usada
en el primer ejemplo:
Como puede ver, los campos predeterminados incluyen Html , Component y Url (además de StatsService que
hemos insertado). Si, por ejemplo, quisiera reemplazar las aplicaciones auxiliares de HTML predeterminadas
con las suyas propias, puede hacerlo fácilmente mediante @inject :
@using System.Threading.Tasks
@using ViewInjectSample.Helpers
@inject MyHtmlHelper Html
<!DOCTYPE html>
<html>
<head>
<title>My Helper</title>
</head>
<body>
<div>
Test: @Html.Value
</div>
</body>
</html>
Si quiere ampliar los servicios existentes, simplemente puede usar esta técnica al heredar o encapsular la
implementación existente con la suya propia.
Vea también
Blog de Simon Timms: Getting Lookup Data Into Your View (Obtener datos de búsqueda en la vista)
Componentes de vista en ASP.NET Core
25/06/2018 • 18 minutes to read • Edit Online
Componentes de vista
Los componentes de vista son similares a las vistas parciales, pero mucho más eficaces. Los componentes de
vista no usan el enlace de modelos y solo dependen de los datos proporcionados cuando se les llama. Este
artículo se escribió en torno a ASP.NET Core MVC, pero los componentes de vista también funcionan con
páginas de Razor.
Un componente de vista:
Representa un fragmento en lugar de una respuesta completa.
Incluye las mismas ventajas de separación de conceptos y capacidad de prueba que se encuentran entre un
controlador y una vista.
Puede tener parámetros y lógica de negocios.
Normalmente se invoca desde una página de diseño.
Los componentes de vista están diseñados para cualquier lugar que tenga lógica de representación reutilizable
demasiado compleja para una vista parcial, como:
Menús de navegación dinámica
Nube de etiquetas (donde consulta la base de datos)
Panel de inicio de sesión
Carro de la compra
Artículos publicados recientemente
Contenido de la barra lateral de un blog típico
Un panel de inicio de sesión que se representa en cada página y muestra los vínculos para iniciar o cerrar
sesión, según el estado del usuario
Un componente de vista consta de dos partes: la clase (normalmente derivada de ViewComponent) y el
resultado que devuelve (por lo general, una vista). Al igual que los controladores, un componente de vista puede
ser un POCO, pero la mayoría de los desarrolladores prefieren aprovechar las ventajas que ofrecen los métodos
y las propiedades disponibles al derivar de ViewComponent .
Los parámetros de clase y método con grafía Pascal para las aplicaciones auxiliares de etiquetas se convierten a
su grafía kebab en minúsculas. La aplicación auxiliar de etiquetas que va a invocar un componente de vista usa
el elemento <vc></vc> . El componente de vista se especifica de la manera siguiente:
<vc:[view-component-name]
parameter1="parameter1 value"
parameter2="parameter2 value">
</vc:[view-component-name]>
Nota: Para poder usar un componente de vista como una aplicación auxiliar de etiquetas, debe registrar el
ensamblado que contiene el componente de vista mediante la directiva @addTagHelper . Por ejemplo, si el
componente de vista está en un ensamblado denominado "MyWebApp", agregue la directiva siguiente al
archivo _ViewImports.cshtml :
@addTagHelper *, MyWebApp
Puede registrar un componente de vista como una aplicación auxiliar de etiquetas en cualquier archivo que
haga referencia al componente de vista. Vea Administración del ámbito de las aplicaciones auxiliares de
etiquetas para más información sobre cómo registrar aplicaciones auxiliares de etiquetas.
El método InvokeAsync usado en este tutorial:
namespace ViewComponentSample.ViewComponents
{
public class PriorityListViewComponent : ViewComponent
{
private readonly ToDoContext db;
[ViewComponent(Name = "PriorityList")]
public class XYZ : ViewComponent
</table>
<div>
@await Component.InvokeAsync("PriorityList", new { maxPriority = 2, isDone = false })
</div>
El marcado @await Component.InvokeAsync muestra la sintaxis para llamar a los componentes de vista. El primer
argumento es el nombre del componente que se quiere invocar o llamar. Los parámetros siguientes se pasan al
componente. InvokeAsync puede tomar un número arbitrario de argumentos.
Pruebe la aplicación. En la imagen siguiente se muestra la lista de tareas pendientes y los elementos de
prioridad:
También puede llamar al componente de vista directamente desde el controlador:
@model IEnumerable<ViewComponentSample.Models.TodoItem>
Actualice Views/TodoList/Index.cshtml:
@await Component.InvokeAsync("PriorityList", new { maxPriority = 4, isDone = true })
Si la vista PVC no se representa, compruebe que está llamando al componente de vista con prioridad cuatro o
superior.
Examinar la ruta de acceso de la vista
Cambie el parámetro de prioridad a tres o menos para que no se devuelva la vista de prioridad.
Cambie temporalmente el nombre de Views/Todo/Components/PriorityList/Default.cshtml a
1Default.cshtml.
Pruebe la aplicación. Obtendrá el siguiente error:
Copie Views/Todo/Components/PriorityList/1Default.cshtml en
Views/Shared/Components/PriorityList/Default.cshtml.
Agregue algún marcado a la vista de componentes de vista de la lista de tareas pendientes Shared para
indicar que la vista está en la carpeta Shared.
Pruebe la vista de componentes Shared.
Evitar cadenas mágicas
Si busca seguridad en tiempo de compilación, puede reemplazar el nombre del componente de vista codificado
de forma rígida por el nombre de clase. Cree el componente de vista sin el sufijo "ViewComponent":
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ViewComponentSample.Models;
namespace ViewComponentSample.ViewComponents
{
public class PriorityList : ViewComponent
{
private readonly ToDoContext db;
Agregue una instrucción using para su archivo de vista de Razor y use el operador nameof :
@using ViewComponentSample.Models
@using ViewComponentSample.ViewComponents
@model IEnumerable<TodoItem>
<h2>ToDo nameof</h2>
<!-- Markup removed for brevity. -->
<div>
Recursos adicionales
Inserción de dependencias en vistas
Control de solicitudes con controladores en ASP.NET
Core MVC
25/06/2018 • 11 minutes to read • Edit Online
¿Qué es un controlador?
Los controladores se usan para definir y agrupar un conjunto de acciones. Una acción (o método de acción) es un
método en un controlador que controla las solicitudes. Los controladores agrupan lógicamente acciones similares.
Esta agregación de acciones permite aplicar de forma colectiva conjuntos comunes de reglas, como el
enrutamiento, el almacenamiento en caché y la autorización. Las solicitudes se asignan a acciones mediante el
enrutamiento.
Por convención, las clases de controlador:
Residen en la carpeta Controllers del nivel de raíz del proyecto.
Heredan de Microsoft.AspNetCore.Mvc.Controller .
Un controlador es una clase instanciable en la que se cumple al menos una de las siguientes condiciones:
El nombre de clase tiene el sufijo "Controller".
La clase hereda de una clase cuyo nombre tiene el sufijo "Controller".
La clase está decorada con el atributo [Controller] .
Una clase de controlador no debe tener asociado un atributo [NonController] .
Los controladores deben seguir el principio de dependencias explícitas. Existen dos maneras de implementar este
principio. Si varias acciones de controlador requieren el mismo servicio, considere la posibilidad de usar la
inserción de constructores para solicitar esas dependencias. Si el servicio solo lo requiere un único método de
acción, considere la posibilidad de usar la inserción de acciones para solicitar la dependencia.
En el patrón de Model-View -Controller, un controlador se encarga del procesamiento inicial de la solicitud y la
creación de instancias del modelo. Por lo general, las decisiones empresariales deben realizarse dentro del
modelo.
El controlador toma el resultado del procesamiento del modelo (si existe) y devuelve o bien la vista correcta y sus
datos de vista asociados, o bien el resultado de la llamada API. Obtenga más información en Información general
de ASP.NET Core MVC e Introducción a ASP.NET Core MVC y Visual Studio.
El controlador es una abstracción de nivel de interfaz de usuario. Se encarga de garantizar que los datos de la
solicitud son válidos y de elegir qué vista (o resultado de una API) se debe devolver. En las aplicaciones
factorizadas correctamente, no incluye directamente acceso a datos ni lógica de negocios. En su lugar, el
controlador delega en servicios el control de estas responsabilidades.
Definir acciones
Los métodos públicos de un controlador, excepto los que están decorados con el atributo [NonAction] , son
acciones. Los parámetros de las acciones están enlazados a los datos de la solicitud y se validan mediante el
enlace de modelos. La validación de modelos se lleva a cabo para todo lo que está enlazado a un modelo. El valor
de la propiedad ModelState.IsValid indica si el enlace de modelos y la validación se han realizado correctamente.
Los métodos de acción deben contener lógica para asignar una solicitud a una cuestión empresarial.
Normalmente las cuestiones empresariales se deben representar como servicios a los que el controlador obtiene
acceso mediante la inserción de dependencias. Después, las acciones asignan el resultado de la acción empresarial
a un estado de aplicación.
Las acciones pueden devolver de todo, pero suelen devolver una instancia de IActionResult (o
Task<IActionResult> para métodos asincrónicos) que genera una respuesta. El método de acción se encarga de
elegir el tipo de respuesta. El resultado de la acción se encarga de la respuesta.
Métodos auxiliares de controlador
Los controladores normalmente heredan de Controller, aunque esto no es necesario. Al derivar de Controller , se
proporciona acceso a tres categorías de métodos auxiliares:
1. Métodos que producen un cuerpo de respuesta vacío
No se incluye ningún encabezado de respuesta HTTP Content-Type , ya que el cuerpo de la respuesta no tiene
contenido que describir.
Hay dos tipos de resultados en esta categoría: redireccionamiento y código de estado HTTP.
Código de estado HTTP
Este tipo devuelve un código de estado HTTP. BadRequest , NotFound y Ok son ejemplos de métodos
auxiliares de este tipo. Por ejemplo, return BadRequest(); genera un código de estado 400 cuando se
ejecuta. Cuando métodos como BadRequest , NotFound y Ok están sobrecargados, ya no se consideran
respondedores de código de estado HTTP, dado que se lleva a cabo una negociación de contenido.
Redireccionamiento
Este tipo devuelve un redireccionamiento a una acción o destino (mediante Redirect , LocalRedirect ,
RedirectToAction o RedirectToRoute ). Por ejemplo, return RedirectToAction("Complete", new {id = 123});
pasa un objeto anónimo y redirige a Complete .
El tipo de resultado de redireccionamiento difiere del tipo de código de estado HTTP principalmente en que
se agrega un encabezado de respuesta HTTP Location .
2. Métodos que producen un cuerpo de respuesta no vacío con un tipo de contenido predefinido
La mayoría de los métodos auxiliares de esta categoría incluye una propiedad ContentType , lo que permite
establecer el encabezado de respuesta Content-Type para describir el cuerpo de la respuesta.
Hay dos tipos de resultados en esta categoría: vista y respuesta con formato.
Vista
Este tipo devuelve una vista que usa un modelo para representar HTML. Por ejemplo,
return View(customer); pasa un modelo a la vista para el enlace de datos.
app.UseMvc(routes =>
{
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
Dentro de la llamada a UseMvc , MapRoute se utiliza para crear una ruta única, a la que nos referiremos
como la ruta default . La mayoría de las aplicaciones MVC usarán una ruta con una plantilla similar a la
ruta default .
La plantilla de ruta "{controller=Home}/{action=Index}/{id?}" puede coincidir con una ruta de dirección
URL como /Products/Details/5 y extraerá los valores de ruta
{ controller = Products, action = Details, id = 5 } mediante la conversión de la ruta en tokens. MVC
intentará encontrar un controlador denominado ProductsController y ejecutar la acción Details :
Tenga en cuenta que, en este ejemplo, el enlace de modelos usaría el valor de id = 5 para establecer el
parámetro id en 5 al invocar esta acción. Consulte Enlace de modelos para obtener más detalles.
Utilizando la ruta default :
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
La plantilla de ruta:
{controller=Home} define Home como el valor controller predeterminado
{action=Index} define Index como el valor action predeterminado
{id?} define id como opcional
No es necesario que los parámetros de ruta opcionales y predeterminados estén presentes en la ruta de
dirección URL para una coincidencia. Consulte Referencia de plantilla de ruta para obtener una
descripción detallada de la sintaxis de la plantilla de ruta.
"{controller=Home}/{action=Index}/{id?}" puede coincidir con la ruta de dirección URL / y generará
los valores de ruta { controller = Home, action = Index } . Los valores de controller y action utilizan
los valores predeterminados, id no genera un valor porque no hay ningún segmento correspondiente
en la ruta de dirección URL. MVC utilizaría estos valores de ruta para seleccionar HomeController y la
acción Index :
/Home/Index
/Home
app.UseMvcWithDefaultRoute();
app.UseMvc(routes =>
{
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
UseMvc no define directamente ninguna ruta, sino que agrega un marcador de posición a la colección de
rutas para la ruta attribute . La sobrecarga UseMvc(Action<IRouteBuilder>) le permite agregar sus
propias rutas y también admite el enrutamiento mediante atributos. UseMvc y todas sus variaciones
agregan un marcador de posición para la ruta de atributo (el enrutamiento mediante atributos siempre
está disponible, independientemente de cómo configure UseMvc . UseMvcWithDefaultRoute define una
ruta predeterminada y admite el enrutamiento mediante atributos. La sección Enrutamiento mediante
atributos incluye más detalles sobre este tipo de enrutamiento.
Enrutamiento convencional
La ruta default :
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
TIP
Usar el enrutamiento convencional con la ruta predeterminada permite generar rápidamente la aplicación sin
necesidad de elaborar un nuevo patrón de dirección URL para cada acción que se define. Para una aplicación con
acciones de estilo CRUD, la coherencia en las direcciones URL en todos los controladores puede ayudar a
simplificar el código y hacer que la interfaz de usuario sea más predecible.
WARNING
id se define como opcional mediante la plantilla de ruta, lo que significa que las acciones se pueden ejecutar sin
el identificador proporcionado como parte de la dirección URL. Normalmente lo que ocurrirá si id se omite de la
dirección URL es que el enlace de modelos establecerá en 0 su valor y, como resultado, no se encontrará
ninguna entidad en la base de datos que coincida con id == 0 . El enrutamiento mediante atributos proporciona
un mayor control que permite requerir el identificador para algunas acciones y para otras no. Por convención, la
documentación incluirá parámetros opcionales como id cuando sea más probable que su uso sea correcto.
Varias rutas
Para agregar varias rutas dentro de UseMvc , agregue más llamadas a MapRoute . Hacerlo le permite
definir varias convenciones o agregar rutas convencionales que se dedican a una acción específica, como
en el ejemplo siguiente:
app.UseMvc(routes =>
{
routes.MapRoute("blog", "blog/{*article}",
defaults: new { controller = "Blog", action = "Article" });
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
Aquí, la ruta blog es una ruta convencional dedicada, lo que significa que utiliza el sistema de
enrutamiento convencional, pero que está dedicada a una acción específica. Puesto que controller y
action no aparecen en la plantilla de ruta como parámetros, solo pueden tener los valores
predeterminados y, por tanto, esta ruta siempre se asignará a la acción BlogController.Article .
Las rutas de la colección de rutas están ordenadas y se procesan en el orden en que se hayan agregado.
Por tanto, la ruta blog de este ejemplo se intentará antes que la ruta default .
NOTE
Las rutas convencionales dedicadas suelen usar parámetros de ruta comodín como {*article} para capturar
la parte restante de la ruta de dirección URL. Esto puede hacer que la ruta sea "demasiado expansiva", lo que
significa que coincidirá con direcciones URL que se pretendía que coincidieran con otras rutas. Coloque las rutas
"expansivas" más adelante en la tabla de rutas para resolver este problema.
Reserva
Como parte del procesamiento de la solicitud, MVC comprobará que los valores de ruta se pueden usar
para buscar un controlador y la acción en la aplicación. Si los valores de ruta no coinciden con una
acción, entonces la ruta no se considera una coincidencia y se intentará la ruta siguiente. Esto se
denomina reserva, y se ha diseñado para simplificar los casos donde se superponen rutas
convencionales.
Eliminar la ambigüedad de acciones
Cuando dos acciones coinciden en el enrutamiento, MVC debe eliminar la ambigüedad para elegir el
candidato "recomendado", o de lo contrario se produce una excepción. Por ejemplo:
public class ProductsController : Controller
{
public IActionResult Edit(int id) { ... }
[HttpPost]
public IActionResult Edit(int id, Product product) { ... }
}
Este controlador define dos acciones que coincidirían con la ruta de dirección URL /Products/Edit/17 y
los datos de ruta { controller = Products, action = Edit, id = 17 } . Se trata de un patrón típico para
controladores MVC donde Edit(int) muestra un formulario para editar un producto, y
Edit(int, Product) procesa el formulario expuesto. Para hacer esto posible, MVC necesita seleccionar
Edit(int, Product) cuando la solicitud es HTTP POST y Edit(int) cuando el verbo HTTP es cualquier
otra cosa.
HttpPostAttribute ( [HttpPost] ) es una implementación de IActionConstraint que solo permitirá que
la acción se seleccione cuando el verbo HTTP sea POST . La presencia de IActionConstraint hace que
Edit(int, Product) sea una mejor coincidencia que Edit(int) , por lo que Edit(int, Product) se
intentará en primer lugar.
Solo necesitará escribir implementaciones de IActionConstraint personalizadas en escenarios
especializados, pero es importante comprender el rol de atributos como HttpPostAttribute (para otros
verbos HTTP se definen atributos similares). En el enrutamiento convencional es común que las acciones
utilicen el mismo nombre de acción cuando son parte de un flujo de trabajo show form -> submit form .
La comodidad de este patrón será más evidente después de revisar la sección Descripción de
IActionConstraint.
Si coinciden varias rutas y MVC no encuentra una ruta "recomendada", se produce una excepción
AmbiguousActionException .
Nombres de ruta
Las cadenas "blog" y "default" de los ejemplos siguientes son nombres de ruta:
app.UseMvc(routes =>
{
routes.MapRoute("blog", "blog/{*article}",
defaults: new { controller = "Blog", action = "Article" });
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
Los nombres de ruta proporcionan un nombre lógico a la ruta para que la ruta con nombre pueda
utilizarse en la generación de direcciones URL. Esto simplifica en gran medida la creación de direcciones
URL en los casos en que el orden de las rutas podría complicar la generación de direcciones URL. Los
nombres de ruta deben ser únicos en toda la aplicación.
Los nombres de ruta no tienen ningún impacto en la coincidencia de direcciones URL ni en el control de
las solicitudes; se utilizan únicamente para la generación de direcciones URL. En Enrutamiento se incluye
información más detallada sobre la generación de direcciones URL, como la generación de direcciones
URL en aplicaciones auxiliares específicas de MVC.
La acción HomeController.Index() se ejecutará para cualquiera de las rutas de dirección URL / , /Home
o /Home/Index .
NOTE
Este ejemplo resalta una diferencia clave de programación entre el enrutamiento mediante atributos y el
enrutamiento convencional. El enrutamiento mediante atributos requiere más entradas para especificar una ruta;
la ruta predeterminada convencional controla las rutas de manera más sucinta. Pero el enrutamiento mediante
atributos permite (y requiere) un control preciso de las plantillas de ruta que se aplicarán a cada acción.
Con el enrutamiento mediante atributos, el nombre del controlador y los nombres de acción no juegan
ningún papel en la selección de las acciones. Este ejemplo coincidirá con las mismas direcciones URL
que el ejemplo anterior.
[HttpGet("/products")]
public IActionResult ListProducts()
{
// ...
}
[HttpPost("/products")]
public IActionResult CreateProduct(...)
{
// ...
}
Para una ruta de dirección URL como /products , la acción ProductsApi.ListProducts se ejecutará
cuando el verbo HTTP sea GET y ProductsApi.CreateProduct se ejecutará cuando el verbo HTTP sea
POST . El enrutamiento mediante atributos primero busca una coincidencia de la dirección URL en el
conjunto de plantillas de ruta que se definen mediante atributos de ruta. Una vez que se encuentra una
plantilla de ruta que coincide, las restricciones IActionConstraint se aplican para determinar qué
acciones se pueden ejecutar.
TIP
Cuando se compila una API de REST, es poco frecuente que se quiera usar [Route(...)] en un método de
acción. Es mejor usar Http*Verb*Attributes más específicos para precisar lo que es compatible con la API. Se
espera que los clientes de API de REST sepan qué rutas y verbos HTTP se asignan a determinadas operaciones
lógicas.
Puesto que una ruta de atributo se aplica a una acción específica, es fácil crear parámetros necesarios
como parte de la definición de plantilla de ruta. En este ejemplo, se requiere id como parte de la ruta
de dirección URL.
La acción ProductsApi.GetProduct(int) se ejecutará para una ruta de dirección URL como /products/3 ,
pero no para una ruta de dirección URL como /products . Consulte Enrutamiento para obtener una
descripción completa de las plantillas de ruta y las opciones relacionadas.
Nombre de ruta
El código siguiente define un nombre de ruta de Products_List :
Los nombres de ruta se pueden utilizar para generar una dirección URL basada en una ruta específica.
Los nombres de ruta no tienen ningún impacto en el comportamiento de coincidencia de direcciones
URL del enrutamiento, y solo se usan para la generación de direcciones URL. Los nombres de ruta
deben ser únicos en toda la aplicación.
NOTE
Compare esto con la ruta predeterminada convencional, que define el parámetro id como opcional ( {id?} ).
Esta capacidad de especificar con precisión las API tiene sus ventajas, como permitir que /products y
/products/5 se envíen a acciones diferentes.
Combinación de rutas
Para que el enrutamiento mediante atributos sea menos repetitivo, los atributos de ruta del controlador
se combinan con los atributos de ruta de las acciones individuales. Las plantillas de ruta definidas en el
controlador se anteponen a las plantillas de ruta de las acciones. La colocación de un atributo de ruta en
el controlador hace que todas las acciones del controlador usen el enrutamiento mediante atributos.
[Route("products")]
public class ProductsApiController : Controller
{
[HttpGet]
public IActionResult ListProducts() { ... }
[HttpGet("{id}")]
public ActionResult GetProduct(int id) { ... }
}
En este ejemplo, la ruta de dirección URL /products puede coincidir con ProductsApi.ListProducts y la
ruta de dirección URL /products/5 puede coincidir con ProductsApi.GetProduct(int) . Ambas acciones
coinciden solo con HTTP GET porque incluyen HttpGetAttribute .
Las plantillas de ruta aplicadas a una acción que comienzan por / no se combinan con las plantillas de
ruta que se aplican al controlador. En este ejemplo coinciden un conjunto de rutas de dirección URL
similares a la ruta predeterminada.
[Route("Home")]
public class HomeController : Controller
{
[Route("")] // Combines to define the route template "Home"
[Route("Index")] // Combines to define the route template "Home/Index"
[Route("/")] // Doesn't combine, defines the route template ""
public IActionResult Index()
{
ViewData["Message"] = "Home index";
var url = Url.Action("Index", "Home");
ViewData["Message"] = "Home index" + "var url = Url.Action; = " + url;
return View();
}
TIP
Evite depender de Order . Si su espacio de direcciones URL requiere unos valores de orden explícitos para un
enrutamiento correcto, es probable que también sea confuso para los clientes. Por lo general, el enrutamiento
mediante atributos seleccionará la ruta correcta con la coincidencia de dirección URL. Si el orden predeterminado
que se usa para la generación de direcciones URL no funciona, normalmente es más sencillo utilizar el nombre de
ruta como una invalidación que aplicar la propiedad Order .
El reemplazo del token se produce como último paso de la creación de las rutas de atributo. El ejemplo
anterior se comportará igual que el código siguiente:
Las rutas de atributo también se pueden combinar con la herencia. Esto es especialmente eficaz si se
combina con el reemplazo de token.
[Route("api/[controller]")]
public abstract class MyBaseController : Controller { ... }
El reemplazo de token también se aplica a los nombres de ruta definidos por las rutas de atributo.
[Route("[controller]/[action]", Name="[controller]_[action]")] generará un nombre de ruta único para
cada acción.
Para que el delimitador de reemplazo de token [ o ] coincida, repita el carácter ( [[ o ]] ) para
usarlo como secuencia de escape.
Varias rutas
El enrutamiento mediante atributos permite definir varias rutas que llegan a la misma acción. El uso más
común de esto es imitar el comportamiento de la ruta convencional predeterminada tal como se
muestra en el ejemplo siguiente:
[Route("[controller]")]
public class ProductsController : Controller
{
[Route("")] // Matches 'Products'
[Route("Index")] // Matches 'Products/Index'
public IActionResult Index()
}
La colocación de varios atributos de ruta en el controlador significa que cada uno de ellos se combinará
con cada uno de los atributos de ruta en los métodos de acción.
[Route("Store")]
[Route("[controller]")]
public class ProductsController : Controller
{
[HttpPost("Buy")] // Matches 'Products/Buy' and 'Store/Buy'
[HttpPost("Checkout")] // Matches 'Products/Checkout' and 'Store/Checkout'
public IActionResult Buy()
}
Cuando en una acción se colocan varios atributos de ruta (que implementan IActionConstraint ), cada
restricción de acción se combina con la plantilla de ruta del atributo que lo define.
[Route("api/[controller]")]
public class ProductsController : Controller
{
[HttpPut("Buy")] // Matches PUT 'api/Products/Buy'
[HttpPost("Checkout")] // Matches POST 'api/Products/Checkout'
public IActionResult Buy()
}
TIP
Si bien el uso de varias rutas en acciones puede parecer eficaz, es mejor que el espacio de direcciones URL de la
aplicación sea sencillo y esté bien definido. Utilice varias rutas en acciones solo cuando sea necesario, por ejemplo,
para admitir a clientes existentes.
[HttpPost("product/{id:int}")]
public IActionResult ShowProduct(int id)
{
// ...
}
Consulte Referencia de plantilla de ruta para obtener una descripción detallada de la sintaxis de la
plantilla de ruta.
Atributos de ruta personalizados con IRouteTemplateProvider
// Use the namespace and controller name to infer a route for the controller.
//
// Example:
//
// controller.ControllerTypeInfo -> "My.Application.Admin.UsersController"
// baseNamespace -> "My.Application"
//
// template => "Admin/[controller]"
//
// This makes your routes roughly line up with the folder structure of your project.
//
var namespc = controller.ControllerType.Namespace;
if (namespc == null)
return;
var template = new StringBuilder();
template.Append(namespc, _baseNamespace.Length + 1,
namespc.Length - _baseNamespace.Length - 1);
template.Replace('.', '/');
template.Append("/[controller]");
NOTE
Lo que distingue los dos tipos de sistemas de enrutamiento es el proceso que se aplica después de que una
dirección URL coincida con una plantilla de ruta. En el enrutamiento convencional, los valores de ruta de la
coincidencia se usan para elegir la acción y el controlador en una tabla de búsqueda de todas las acciones
enrutadas convencionales. En el enrutamiento mediante atributos, cada plantilla ya está asociada a una acción y
no es necesaria ninguna otra búsqueda.
using Microsoft.AspNetCore.Mvc;
Si la aplicación está usando la ruta convencional predeterminada, el valor de la variable url será la
cadena de ruta de dirección URL /UrlGeneration/Destination . Esta ruta de dirección URL se crea por
enrutamiento mediante la combinación de los valores de ruta de la solicitud actual (valores de ambiente)
con los valores pasados a Url.Action , y sustituyendo esos valores en la plantilla de ruta:
result: /UrlGeneration/Destination
El valor de cada uno de los parámetros de ruta incluidos en la plantilla de ruta se sustituye por nombres
que coincidan con los valores y los valores de ambiente. Si un parámetro de ruta no tiene un valor, puede
utilizar un valor predeterminado en caso de tenerlo, o se puede omitir si es opcional (como en el caso de
id en este ejemplo). La generación de direcciones URL producirá un error si cualquiera de los
parámetros de ruta necesarios no tiene un valor correspondiente. Si se produce un error en la
generación de direcciones URL para una ruta, se prueba con la ruta siguiente hasta que se hayan
probado todas las rutas o se encuentra una coincidencia.
En el ejemplo anterior de Url.Action se supone que el enrutamiento es convencional, pero la
generación de direcciones URL funciona del mismo modo con el enrutamiento mediante atributos, si
bien los conceptos son diferentes. En el enrutamiento convencional, los valores de ruta se usan para
expandir una plantilla; los valores de ruta de controller y action suelen aparecer en esa plantilla. Esto
es válido porque las direcciones URL que coinciden con el enrutamiento se adhieren a una convención.
En el enrutamiento mediante atributos, no está permitido que los valores de ruta de controller y
action aparezcan en la plantilla. En vez de eso, se utilizan para buscar la plantilla que se va a usar.
// In Startup class
public void Configure(IApplicationBuilder app)
{
app.UseMvc();
}
using Microsoft.AspNetCore.Mvc;
[HttpGet("custom/url/to/destination")]
public IActionResult Destination() {
return View();
}
}
MVC genera una tabla de búsqueda de todas las acciones enrutadas mediante atributos y hará coincidir
los valores controller y action para seleccionar la plantilla de ruta que se usará para la generación de
direcciones URL. En el ejemplo anterior se genera custom/url/to/destination .
Generación de direcciones URL por nombre de acción
Url.Action ( IUrlHelper . Action ) y todas las sobrecargas relacionadas se basan en la idea de
especificar el destino del vínculo mediante un nombre de controlador y un nombre de acción.
NOTE
Cuando se usa Url.Action , se especifican los valores actuales de la ruta para controller y action . Los
valores de controller y action forman parte tanto de los valores de ambiente ycomo de los valores. El
método Url.Action siempre utiliza los valores actuales de action y controller y genera una ruta de
dirección URL que dirige a la acción actual.
Intentos de enrutamiento para utilizar los valores en los valores de ambiente para rellenar la información
que no se proporcionó al generar una dirección URL. Al utilizar una ruta como {a}/{b}/{c}/{d} y los
valores de ambiente { a = Alice, b = Bob, c = Carol, d = David } , el enrutamiento tiene suficiente
información para generar una dirección URL sin ningún valor adicional, puesto que todos los
parámetros de ruta tienen un valor. Si se agregó el valor { d = Donovan } , el valor { d = David } se
ignorará y la ruta de dirección URL generada será Alice/Bob/Carol/Donovan .
WARNING
Las rutas de dirección URL son jerárquicas. En el ejemplo anterior, si se agrega el valor { c = Cheryl } , ambos
valores { c = Carol, d = David } se ignorarán. En este caso ya no tenemos un valor para d y se producirá
un error en la generación de direcciones URL. Debe especificar el valor deseado de c y d . Es probable que este
problema se produzca con la ruta predeterminada ( {controller}/{action}/{id?} ), pero raramente encontrará
este comportamiento en la práctica, dado que Url.Action especificará siempre de manera explícita un valor
controller y action .
Las sobrecargas más largas de Url.Action también toman un objeto de valores de ruta adicional para
proporcionar valores para parámetros de ruta distintos de controller y action . Normalmente verá
esto utilizado con id , como Url.Action("Buy", "Products", new { id = 17 }) . Por convención, el objeto
de valores de ruta normalmente es un objeto de tipo anónimo, pero también puede ser IDictionary<> o
un objeto .NET estándar. Los valores de ruta adicionales que no coinciden con los parámetros de ruta se
colocan en la cadena de consulta.
using Microsoft.AspNetCore.Mvc;
TIP
Para crear una dirección URL absoluta, use una sobrecarga que acepte protocol :
Url.Action("Buy", "Products", new { id = 17 }, protocol: Request.Scheme)
Las TagHelper generan direcciones URL a través de la TagHelper form y la TagHelper <a> . Ambos usan
IUrlHelper para su implementación. Consulte Trabajar con formularios para obtener más información.
Dentro de las vistas, IUrlHelper está disponible a través de la propiedad Url para una generación de
direcciones URL ad hoc no cubierta por los pasos anteriores.
Generar direcciones URL en los resultados de acción
Los ejemplos anteriores han demostrado el uso de IUrlHelper en un controlador, aunque el uso más
común de un controlador consiste en generar una dirección URL como parte de un resultado de acción.
Las clases base ControllerBase y Controller proporcionan métodos de conveniencia para los
resultados de acción que hacen referencia a otra acción. Un uso típico es redirigir después de aceptar la
entrada del usuario.
Los patrones de diseño Factory Method de resultados de acción siguen un patrón similar a los métodos
en IUrlHelper .
Caso especial para rutas convencionales dedicadas
El enrutamiento convencional puede usar un tipo especial de definición de ruta denominada ruta
convencional dedicada. En el ejemplo siguiente, la ruta llamada blog es una ruta convencional
dedicada.
app.UseMvc(routes =>
{
routes.MapRoute("blog", "blog/{*article}",
defaults: new { controller = "Blog", action = "Article" });
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
Con estas definiciones de ruta, Url.Action("Index", "Home") generará la ruta de dirección URL / con la
ruta default . Pero, ¿por qué? Se puede suponer que los valores de ruta
{ controller = Home, action = Index } son suficientes para generar una dirección URL utilizando blog ,
con el resultado /blog?action=Index&controller=Home .
Las rutas convencionales dedicadas se basan en un comportamiento especial de los valores
predeterminados que no tienen un parámetro de ruta correspondiente que impida que la ruta sea
"demasiado expansiva" con la generación de direcciones URL. En este caso, los valores predeterminados
son { controller = Blog, action = Article } , y ni controller ni action aparecen como un parámetro
de ruta. Cuando el enrutamiento realiza la generación de direcciones URL, los valores proporcionados
deben coincidir con los valores predeterminados. La generación de direcciones URL con blog producirá
un error porque los valores { controller = Home, action = Index } no coinciden con
{ controller = Blog, action = Article } . Después, el enrutamiento vuelve para probar default ,
operación que se realiza correctamente.
Áreas
Las áreas son una característica de MVC que se usa para organizar funciones relacionadas en un grupo
como un espacio de nombres de enrutamiento independiente (para acciones de controlador) y una
estructura de carpetas (para las vistas). La utilización de áreas permite que una aplicación tenga varios
controladores con el mismo nombre, siempre y cuando tengan áreas diferentes. El uso de áreas crea una
jerarquía para el enrutamiento mediante la adición de otro parámetro de ruta, area , a controller y
action . En esta sección veremos la interacción entre el enrutamiento y las áreas. Consulte Áreas para
obtener más información sobre cómo se utilizan las áreas con las vistas.
En el ejemplo siguiente se configura MVC para usar la ruta predeterminada convencional y una ruta de
área para un área denominada Blog :
app.UseMvc(routes =>
{
routes.MapAreaRoute("blog_route", "Blog",
"Manage/{controller}/{action}/{id?}");
routes.MapRoute("default_route", "{controller}/{action}/{id?}");
});
Cuando coincide con una ruta de dirección URL como /Manage/Users/AddUser , la primera ruta generará
los valores de ruta { area = Blog, controller = Users, action = AddUser } . El valor de ruta area se
genera con un valor predeterminado para area . De hecho, la ruta creada por MapAreaRoute es
equivalente a la siguiente:
app.UseMvc(routes =>
{
routes.MapRoute("blog_route", "Manage/{controller}/{action}/{id?}",
defaults: new { area = "Blog" }, constraints: new { area = "Blog" });
routes.MapRoute("default_route", "{controller}/{action}/{id?}");
});
MapAreaRoute utiliza el nombre de área proporcionado, que en este caso es Blog , para crear una ruta
con un valor predeterminado y una restricción para area . El valor predeterminado garantiza que la ruta
siempre produce { area = Blog, ... } ; la restricción requiere el valor { area = Blog, ... } para la
generación de la dirección URL.
TIP
El enrutamiento convencional depende del orden. En general, las rutas con áreas deben colocarse antes en la tabla
de rutas, ya que son más específicas que las rutas sin un área.
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace1
{
[Area("Blog")]
public class UsersController : Controller
{
public IActionResult AddUser()
{
return View();
}
}
}
AreaAttribute es lo que denota un controlador como parte de un área. Se dice que este controlador está
en el área Blog . Los controladores sin un atributo [Area] no son miembros de ningún área y no
coincidirán cuando el enrutamiento proporcione el valor de ruta area . En el ejemplo siguiente, solo el
primer controlador enumerado puede coincidir con los valores de ruta
{ area = Blog, controller = Users, action = AddUser } .
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace1
{
[Area("Blog")]
public class UsersController : Controller
{
public IActionResult AddUser()
{
return View();
}
}
}
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace2
{
// Matches { area = Zebra, controller = Users, action = AddUser }
[Area("Zebra")]
public class UsersController : Controller
{
public IActionResult AddUser()
{
return View();
}
}
}
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace3
{
// Matches { area = string.Empty, controller = Users, action = AddUser }
// Matches { area = null, controller = Users, action = AddUser }
// Matches { controller = Users, action = AddUser }
public class UsersController : Controller
{
public IActionResult AddUser()
{
return View();
}
}
}
NOTE
En aras de la exhaustividad, aquí se muestra el espacio de nombres de cada controlador; en caso contrario, los
controladores tendrían un conflicto de nomenclatura y generarían un error del compilador. Los espacios de
nombres de clase no tienen ningún efecto en el enrutamiento de MVC.
Los dos primeros controladores son miembros de las áreas y solo coinciden cuando el valor de ruta
area proporciona su respectivo nombre de área. El tercer controlador no es miembro de ningún área y
solo puede coincidir cuando el enrutamiento no proporciona ningún valor para area .
NOTE
En términos de búsqueda de coincidencias de ningún valor, la ausencia del valor area es igual que si el valor de
area fuese null o una cadena vacía.
Al ejecutar una acción en un área, el valor de ruta para area estará disponible como un valor de
ambiente para que el enrutamiento pueda usarlo en la generación de direcciones URL. Esto significa
que, de forma predeterminada, las áreas actúan de forma adhesiva para la generación de direcciones
URL, tal como se muestra en el ejemplo siguiente.
app.UseMvc(routes =>
{
routes.MapAreaRoute("duck_route", "Duck",
"Manage/{controller}/{action}/{id?}");
routes.MapRoute("default", "Manage/{controller=Home}/{action=Index}/{id?}");
});
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace4
{
[Area("Duck")]
public class UsersController : Controller
{
public IActionResult GenerateURLInArea()
{
// Uses the 'ambient' value of area
var url = Url.Action("Index", "Home");
// returns /Manage
return Content(url);
}
Descripción de IActionConstraint
NOTE
En esta sección se analiza con mayor profundidad el funcionamiento interno del marco y la elección por parte de
MVC de la acción que se va a ejecutar. Una aplicación típica no necesitará un IActionConstraint personalizado.
Es probable que ya haya usado IActionConstraint incluso si no está familiarizado con la interfaz. El
atributo [HttpGet] y los atributos [Http-VERB] similares implementan IActionConstraint con el fin de
limitar la ejecución de un método de acción.
Adoptando la ruta convencional predeterminada, la ruta de dirección URL /Products/Edit generaría los
valores { controller = Products, action = Edit } , que coincidirían con ambas acciones que se
muestran aquí. En terminología de IActionConstraint , diríamos que ambas acciones se consideran
candidatas, puesto que las dos coinciden con los datos de ruta.
Cuando HttpGetAttribute se ejecute, dirá que Edit() es una coincidencia para GET, pero no para
cualquier otro verbo HTTP. La acción Edit(...) no tiene ninguna restricción definida, por lo que
coincidirá con cualquier verbo HTTP. Con POST , solamente Edit(...) coincide. Pero con GET ambas
acciones pueden coincidir. No obstante, una acción con IActionConstraint siempre se considera mejor
que una acción sin dicha restricción. Por tanto, como Edit() tiene [HttpGet] , se considera más
específica y se seleccionará si ambas acciones pueden coincidir.
Conceptualmente, IActionConstraint es una forma de sobrecarga, pero en lugar de sobrecargar
métodos con el mismo nombre, la sobrecarga se produce entre acciones que coinciden con la misma
dirección URL. El enrutamiento mediante atributos también utiliza IActionConstraint y puede dar lugar
a que acciones de diferentes controladores se consideren candidatas.
Implementación de IActionConstraint
La manera más sencilla de implementar IActionConstraint consiste en crear una clase derivada de
System.Attribute y colocarla en las acciones y los controladores. MVC detectará automáticamente
cualquier IActionConstraint que se aplique como atributo. El modelo de aplicaciones es quizá el
enfoque más flexible para la aplicación de restricciones, puesto que permite metaprogramar cómo se
aplican.
En el ejemplo siguiente, una restricción elige una acción según un código de país de los datos de ruta. El
ejemplo completo se encuentra en GitHub.
Usted es responsable de implementar el método Accept y elegir un valor para "Order" para que la
restricción se ejecute. En este caso, el método Accept devuelve true para denotar que la acción es una
coincidencia cuando el valor de ruta country coincide. Esto difiere de RouteValueAttribute en que
permite volver a una acción sin atributos. El ejemplo muestra que si se define una acción en-US ,
entonces un código de país como fr-FR recurriría a un controlador más genérico que no tenga
[CountrySpecific(...)] aplicado.
La propiedad Order decide de qué fase forma parte la restricción. Restricciones de acciones que se
ejecutan en grupos basados en la Order . Por ejemplo, todos los atributos del método HTTP
proporcionados por el marco utilizan el mismo valor Order para que se ejecuten en la misma fase.
Puede tener las fases que sean necesarias para implementar las directivas deseadas.
TIP
Para decidir el valor de Order , piense si la restricción se debería o no aplicar antes que los métodos HTTP. Los
números más bajos se ejecutan primero.
Cargas de archivos en ASP.NET Core
25/06/2018 • 13 minutes to read • Edit Online
Para admitir las cargas de archivos, los formularios HTML deben especificar un tipo enctype de
multipart/form-data . El elemento de entrada files de arriba admite la carga de varios archivos. Para admitir un
único archivo cargado, solo hay que omitir el atributo multiple de este elemento de entrada. El marcado anterior
se muestra así en un explorador:
Se puede tener acceso a los archivos individuales cargados en el servidor a través del enlace de modelos, por
medio de la interfaz IFormFile. IFormFile tiene la siguiente estructura:
public interface IFormFile
{
string ContentType { get; }
string ContentDisposition { get; }
IHeaderDictionary Headers { get; }
long Length { get; }
string Name { get; }
string FileName { get; }
Stream OpenReadStream();
void CopyTo(Stream target);
Task CopyToAsync(Stream target, CancellationToken cancellationToken = null);
}
WARNING
No se base o confíe en la propiedad FileName sin validarla. La propiedad FileName solo se debe usar con fines ilustrativos.
Al cargar archivos por medio del enlace de modelos y la interfaz IFormFile , el método de acción puede aceptar
bien un solo IFormFile , bien un IEnumerable<IFormFile> (o List<IFormFile> ) que represente varios archivos. En el
siguiente ejemplo se itera por uno o varios archivos cargados, estos se guardan en el sistema de archivos local y se
devuelve el número total y el tamaño de los archivos cargados.
Advertencia: el siguiente código utiliza GetTempFileName , que produce una IOException si no se crean más de
65535 archivos sin eliminar los archivos temporales anteriores. Debe eliminar los archivos temporales o usar una
aplicación real GetTempPath y GetRandomFileName para crear nombres de archivo temporal. El límite de 65535
archivos es por servidor, por lo que puede usar otra aplicación en el servidor de seguridad de todos los archivos de
65535.
[HttpPost("UploadFiles")]
public async Task<IActionResult> Post(List<IFormFile> files)
{
long size = files.Sum(f => f.Length);
Los archivos que se cargan usando la técnica IFormFile se almacenan en búfer en memoria o en disco en el
servidor web antes de procesarse. Dentro del método de acción, se puede tener acceso al contenido de IFormFile
como una secuencia. Aparte de al sistema de archivos local, los archivos se pueden transmitir por streaming
también a Azure Blob Storage o a Entity Framework.
Para almacenar datos de archivo binario en una base de datos con Entity Framework, defina una propiedad de tipo
byte[] en la entidad:
NOTE
IFormFile se puede usar directamente como un parámetro de método de acción o como una propiedad ViewModel, tal y
como se aprecia arriba.
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var user = new ApplicationUser {
UserName = model.Email,
Email = model.Email
};
using (var memoryStream = new MemoryStream())
{
await model.AvatarImage.CopyToAsync(memoryStream);
user.AvatarImage = memoryStream.ToArray();
}
// additional logic omitted
NOTE
Tenga cuidado al almacenar los datos binarios en bases de datos relacionales, ya que esto puede repercutir adversamente en
el rendimiento.
NOTE
Cualquier archivo individual almacenado en búfer con un tamaño superior a 64 KB se trasladará desde la memoria RAM a un
archivo temporal en el disco en el servidor. Los recursos (disco, memoria RAM) que se usan en las cargas de archivos
dependen de la cantidad y del tamaño de las cargas de archivos que se realizan simultáneamente. En el streaming lo
importante no es el rendimiento, sino la escala. Si se intentan almacenar demasiadas cargas en búfer, el sitio se bloqueará
cuando se quede sin memoria o sin espacio en disco.
En el siguiente ejemplo se describe cómo usar JavaScript/Angular para transmitir por streaming a una acción de
controlador. El token de antifalsificación del archivo se genera por medio de un atributo de filtro personalizado y se
pasa en encabezados HTTP, en lugar de en el cuerpo de la solicitud. Dado que el método de acción procesa los
datos cargados directamente, el enlace de modelos se deshabilita por otro filtro. Dentro de la acción, el contenido
del formulario se lee usando un MultipartReader (que lee cada MultipartSection individual), de forma que el
archivo se procesa o el contenido se almacena, según corresponda. Una vez que se han leído todas las secciones, la
acción realiza su enlace de modelos particular.
La acción inicial carga el formulario y guarda un token de antifalsificación en una cookie (a través del atributo
GenerateAntiforgeryTokenCookieForAjax ):
[HttpGet]
[GenerateAntiforgeryTokenCookieForAjax]
public IActionResult Index()
{
return View();
}
El atributo usa la compatibilidad de antifalsificación integrada de ASP.NET Core para establecer una cookie con un
token de solicitud:
services.AddMvc();
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var factories = context.ValueProviderFactories;
factories.RemoveType<FormValueProviderFactory>();
factories.RemoveType<JQueryFormValueProviderFactory>();
}
Como el enlace de modelos está deshabilitado, el método de acción Upload no acepta parámetros. Funciona
directamente con la propiedad Request de ControllerBase . Se usa un elemento MultipartReader para leer cada
sección. El archivo se guarda con un nombre de archivo GUID y los datos de clave/valor se almacenan en un
KeyValueAccumulator . Una vez que todas las secciones se han leído, el contenido de KeyValueAccumulator se usa
para enlazar los datos del formulario a un tipo de modelo.
Abajo mostramos el método Upload completo:
Advertencia: el siguiente código utiliza GetTempFileName , que produce una IOException si no se crean más de
65535 archivos sin eliminar los archivos temporales anteriores. Debe eliminar los archivos temporales o usar una
aplicación real GetTempPath y GetRandomFileName para crear nombres de archivo temporal. El límite de 65535
archivos es por servidor, por lo que puede usar otra aplicación en el servidor de seguridad de todos los archivos de
65535.
// 1. Disable the form value model binding here to take control of handling
// potentially large files.
// 2. Typically antiforgery tokens are sent in request body, but since we
// do not want to read the request body early, the tokens are made to be
// sent via headers. The antiforgery token filter first looks for tokens
// in the request header and then falls back to reading the body.
[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Upload()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
return BadRequest($"Expected a multipart request, but got {Request.ContentType}");
}
// Used to accumulate all the form url encoded key value pairs in the
// request.
var formAccumulator = new KeyValueAccumulator();
string targetFilePath = null;
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
if (hasContentDispositionHeader)
{
if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
{
targetFilePath = Path.GetTempFileName();
using (var targetStream = System.IO.File.Create(targetFilePath))
{
await section.Body.CopyToAsync(targetStream);
// Drains any remaining section body that has not been consumed and
// reads the headers for the next section.
section = await reader.ReadNextSectionAsync();
}
Solución de problemas
Aquí incluimos algunos problemas comunes que pueden surgir al cargar archivos, así como sus posibles
soluciones.
Error "No encontrado" inesperado en IIS
El siguiente error indica que la carga de archivos supera el valor de maxAllowedContentLength configurado en el
servidor:
El valor predeterminado es 30000000 , que son alrededor de 28,6 MB. Este valor se puede personalizar editando el
archivo web.config:
<system.webServer>
<security>
<requestFiltering>
<!-- This will handle requests up to 50MB -->
<requestLimits maxAllowedContentLength="52428800" />
</requestFiltering>
</security>
</system.webServer>
Esto solo ocurre en IIS; este comportamiento no sucede de forma predeterminada cuando los archivos se
hospedan en Kestrel. Para más información, vea Request Limits <requestLimits> (Límites de solicitudes).
Excepción de referencia nula con IFormFile
Si el controlador acepta archivos cargados con IFormFile , pero ve que el valor siempre es null, confirme que en el
formulario HTML se especifica un valor enctype de multipart/form-data . Si este atributo no está establecido en el
elemento <form> , la carga de archivos no se llevará a cabo y cualquier argumento IFormFile enlazado será nulo.
Inserción de dependencias en controladores en
ASP.NET Core
25/06/2018 • 9 minutes to read • Edit Online
Inserción de dependencias
La inserción de dependencias es una técnica que sigue el principio de inversión de dependencias, lo que permite
que las aplicaciones consten de módulos de acoplamiento flexible. ASP.NET Core tiene compatibilidad integrada
para inserción de dependencias, lo que facilita las tareas de prueba y mantenimiento de las aplicaciones.
Inserción de constructores
La compatibilidad integrada de ASP.NET Core con la inserción de dependencias basada en constructores se
extiende a los controladores MVC. Simplemente con agregar un tipo de servicio al controlador como un
parámetro de constructor, ASP.NET Core intentará resolver ese tipo mediante su contenedor de servicios
integrado. Normalmente, los servicios se definen mediante interfaces, aunque no siempre es así. Por ejemplo, si
la aplicación tiene lógica de negocios que depende de la hora actual, también se puede insertar un servicio que
recupera la hora (en lugar de codificarla de forma rígida), lo que permitiría superar las pruebas en
implementaciones que usan una hora determinada.
using System;
namespace ControllerDI.Interfaces
{
public interface IDateTime
{
DateTime Now { get; }
}
}
Implementar una interfaz como esta para que utilice el reloj del sistema en tiempo de ejecución no es nada
complicado:
using System;
using ControllerDI.Interfaces;
namespace ControllerDI.Services
{
public class SystemDateTime : IDateTime
{
public DateTime Now
{
get { return DateTime.Now; }
}
}
}
Teniendo esto implementado, podemos utilizar el servicio en el controlador. En este caso, hemos agregado lógica
al método Index de HomeController para presentar un saludo al usuario en función de la hora del día.
using ControllerDI.Interfaces;
using Microsoft.AspNetCore.Mvc;
namespace ControllerDI.Controllers
{
public class HomeController : Controller
{
private readonly IDateTime _dateTime;
Este error se produce cuando no se ha configurado un servicio en el método ConfigureServices de nuestra clase
Startup . Para especificar que las solicitudes de IDateTime deben resolverse mediante una instancia de
SystemDateTime , agregue la línea resaltada en la siguiente lista a su método ConfigureServices :
NOTE
Este servicio en particular podría implementarse mediante cualquiera de las diversas opciones de duración ( Transient ,
Scoped o Singleton ). Consulte Dependency Injection (Inserción de dependencias) para ver cómo afectará al
comportamiento de su servicio cada una de estas opciones de ámbito.
Una vez que se ha configurado el servicio, al ejecutar la aplicación y navegar a la página principal se debería
mostrar el mensaje basado en la hora según lo esperado:
TIP
Vea Testing controller logic (Comprobación de la lógica de controlador) para obtener información sobre cómo solicitar
explícitamente dependencias http://deviq.com/explicit-dependencies-principle/ en controladores para facilitar la
comprobación de código.
La inserción de dependencias integrada de ASP.NET Core es compatible con tener un solo constructor para las
clases que soliciten servicios. Si se tiene más de un constructor, es posible recibir la siguiente excepción:
InvalidOperationException: Multiple constructors accepting all given argument types have been found in type
'ControllerDI.Controllers.HomeController'. There should only be one applicable constructor.
Microsoft.Extensions.DependencyInjection.ActivatorUtilities.FindApplicableConstructor(Type instanceType,
Type[] argumentTypes, ConstructorInfo& matchingConstructor, Nullable`1[]& parameterMap)
Tal como indica el mensaje de error, tener un solo constructor corregiría el problema. También se puede
reemplazar la compatibilidad de inserción de dependencias predeterminada por una implementación de otros
fabricantes que sea compatible con varios constructores.
return View();
}
namespace ControllerDI.Model
{
public class SampleWebSettings
{
public string Title { get; set; }
public int Updates { get; set; }
}
}
A continuación, necesita configurar la aplicación para que use el modelo de opciones y agregar la clase de
configuración a la colección de servicios en ConfigureServices :
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("samplewebsettings.json");
Configuration = builder.Build();
}
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?
LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
// Required to use the Options<T> pattern
services.AddOptions();
services.AddMvc();
NOTE
En la lista anterior, se configura la aplicación para que lea la configuración de un archivo con formato JSON. La
configuración también se puede definir completamente en código, como se muestra en el código comentado anterior.
Consulte Configuración para conocer más opciones de configuración.
Una vez que haya especificado un objeto de configuración fuertemente tipado (en este caso, SampleWebSettings )
y lo haya agregado a la colección de servicios, podrá solicitarlo desde cualquier método de acción o controlador
mediante la solicitud de una instancia de IOptions<T> (en este caso, IOptions<SampleWebSettings> ). El código
siguiente muestra cómo se podría solicitar la configuración desde un controlador:
Pruebas unitarias
Las pruebas unitarias conllevan probar una parte de una aplicación de forma aislada con respecto a su
infraestructura y dependencias. Cuando se realizan pruebas unitarias de la lógica de controlador, solo se
comprueba el contenido de una única acción, no el comportamiento de sus dependencias o del marco en sí.
Cuando realice pruebas unitarias de sus acciones de controlador, asegúrese de que solo se centran en el
comportamiento. Una prueba unitaria de controlador evita tener que recurrir a elementos como los filtros, el
enrutamiento o el enlace de modelos. Al centrarse en comprobar solo una cosa, las pruebas unitarias suelen ser
fáciles de escribir y rápidas de ejecutar. Un conjunto de pruebas unitarias bien escrito se puede ejecutar con
frecuencia sin demasiada sobrecarga. Pero las pruebas unitarias no detectan problemas de interacción entre
componentes, que es el propósito de las pruebas de integración.
Si está escribiendo filtros personalizados, rutas, etc., debería realizar pruebas unitarias en ellos, pero no como parte
de las comprobaciones de una acción de controlador determinada, sino de forma aislada.
TIP
Cree y ejecute pruebas unitarias con Visual Studio.
Para explicar las pruebas unitarias, revisaremos el siguiente controlador. Muestra una lista de sesiones de lluvia de
ideas y permite crear nuevas sesiones de lluvia de ideas con un método POST:
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using TestingControllersSample.Core.Interfaces;
using TestingControllersSample.Core.Model;
using TestingControllersSample.ViewModels;
namespace TestingControllersSample.Controllers
{
public class HomeController : Controller
{
private readonly IBrainstormSessionRepository _sessionRepository;
return View(model);
}
[HttpPost]
public async Task<IActionResult> Index(NewSessionModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
else
{
await _sessionRepository.AddAsync(new BrainstormSession()
{
DateCreated = DateTimeOffset.Now,
Name = model.SessionName
});
}
El controlador sigue el principio de dependencias explícitas, de modo que espera que la inserción de dependencias
le proporcione una instancia de IBrainstormSessionRepository . Esto es bastante sencillo de comprobar si se usa un
marco de objeto ficticio, como Moq. El método HTTP GET Index no tiene bucles ni bifurcaciones y solamente llama
a un método. Para probar este método Index , tenemos que confirmar que se devuelve un ViewResult , con un
ViewModel del método List del repositorio.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Moq;
using TestingControllersSample.Controllers;
using TestingControllersSample.Core.Interfaces;
using TestingControllersSample.Core.Model;
using TestingControllersSample.ViewModels;
using Xunit;
namespace TestingControllersSample.Tests.UnitTests
{
public class HomeControllerTests
{
[Fact]
public async Task Index_ReturnsAViewResult_WithAListOfBrainstormSessions()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.ListAsync()).Returns(Task.FromResult(GetTestSessions()));
var controller = new HomeController(mockRepo.Object);
// Act
var result = await controller.Index();
// Assert
var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsAssignableFrom<IEnumerable<StormSessionViewModel>>(
viewResult.ViewData.Model);
Assert.Equal(2, model.Count());
}
El método HomeController HTTP POST Index (mostrado arriba) debe comprobar lo siguiente:
El método de acción devuelve un ViewResult de solicitud incorrecta con los datos adecuados cuando
ModelState.IsValid es false .
[Fact]
public async Task IndexPost_ReturnsBadRequestResult_WhenModelStateIsInvalid()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.ListAsync()).Returns(Task.FromResult(GetTestSessions()));
var controller = new HomeController(mockRepo.Object);
controller.ModelState.AddModelError("SessionName", "Required");
var newSession = new HomeController.NewSessionModel();
// Act
var result = await controller.Index(newSession);
// Assert
var badRequestResult = Assert.IsType<BadRequestObjectResult>(result);
Assert.IsType<SerializableError>(badRequestResult.Value);
}
[Fact]
public async Task IndexPost_ReturnsARedirectAndAddsSession_WhenModelStateIsValid()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.AddAsync(It.IsAny<BrainstormSession>()))
.Returns(Task.CompletedTask)
.Verifiable();
var controller = new HomeController(mockRepo.Object);
var newSession = new HomeController.NewSessionModel()
{
SessionName = "Test Name"
};
// Act
var result = await controller.Index(newSession);
// Assert
var redirectToActionResult = Assert.IsType<RedirectToActionResult>(result);
Assert.Null(redirectToActionResult.ControllerName);
Assert.Equal("Index", redirectToActionResult.ActionName);
mockRepo.Verify();
}
La primera prueba confirma cuándo ModelState no es válido; se devuelve el mismo ViewResult que para una
solicitud GET . Cabe decir que la prueba no intenta pasar un modelo no válido. Eso no funcionaría de todas formas,
ya que el enlace de modelos no se está ejecutando (aunque una prueba de integración sí usaría el enlace de
modelos). En este caso concreto no estamos comprobando el enlace de modelos. Con estas pruebas unitarias
solamente estamos comprobando lo que el código del método de acción hace.
La segunda prueba comprueba si, cuando ModelState es válido, se agrega un nuevo BrainstormSession (a través
del repositorio) y el método devuelve un RedirectToActionResult con las propiedades que se esperan. Las llamadas
ficticias que no se efectúan se suelen omitir, aunque llamar a Verifiable al final de la llamada nos permite
confirmar esto en la prueba. Esto se logra con una llamada a mockRepo.Verify , que producirá un error en la prueba
si no se ha llamado al método esperado.
NOTE
La biblioteca Moq usada en este ejemplo nos permite mezclar fácilmente objetos ficticios comprobables (o "estrictos") con
objetos ficticios no comprobables (también denominados "flexibles" o stub). Obtenga más información sobre cómo
personalizar el comportamiento de objetos ficticios con Moq.
Otro controlador de la aplicación muestra información relacionada con una sesión de lluvia de ideas determinada.
Este controlador incluye lógica para tratar los valores de identificador no válidos:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using TestingControllersSample.Core.Interfaces;
using TestingControllersSample.ViewModels;
namespace TestingControllersSample.Controllers
{
public class SessionController : Controller
{
private readonly IBrainstormSessionRepository _sessionRepository;
return View(viewModel);
}
}
}
La acción de controlador tiene tres casos que comprobar, uno por cada instrucción return :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Moq;
using TestingControllersSample.Controllers;
using TestingControllersSample.Core.Interfaces;
using TestingControllersSample.Core.Model;
using TestingControllersSample.ViewModels;
using Xunit;
namespace TestingControllersSample.Tests.UnitTests
{
public class SessionControllerTests
{
[Fact]
public async Task IndexReturnsARedirectToIndexHomeWhenIdIsNull()
{
// Arrange
var controller = new SessionController(sessionRepository: null);
// Act
var result = await controller.Index(id: null);
// Assert
var redirectToActionResult = Assert.IsType<RedirectToActionResult>(result);
Assert.Equal("Home", redirectToActionResult.ControllerName);
Assert.Equal("Index", redirectToActionResult.ActionName);
}
[Fact]
public async Task IndexReturnsContentWithSessionNotFoundWhenSessionNotFound()
{
// Arrange
int testSessionId = 1;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.Returns(Task.FromResult((BrainstormSession)null));
var controller = new SessionController(mockRepo.Object);
// Act
var result = await controller.Index(testSessionId);
// Assert
var contentResult = Assert.IsType<ContentResult>(result);
Assert.Equal("Session not found.", contentResult.Content);
}
[Fact]
public async Task IndexReturnsViewResultWithStormSessionViewModel()
{
// Arrange
int testSessionId = 1;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.Returns(Task.FromResult(GetTestSessions().FirstOrDefault(s => s.Id == testSessionId)));
var controller = new SessionController(mockRepo.Object);
// Act
var result = await controller.Index(testSessionId);
// Assert
var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsType<StormSessionViewModel>(viewResult.ViewData.Model);
Assert.Equal("Test One", model.Name);
Assert.Equal(2, model.DateCreated.Day);
Assert.Equal(testSessionId, model.Id);
}
La aplicación expone la funcionalidad como una API web (una lista de ideas asociadas a una sesión de lluvia de
ideas y un método para agregar nuevas ideas a una sesión):
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using TestingControllersSample.ClientModels;
using TestingControllersSample.Core.Interfaces;
using TestingControllersSample.Core.Model;
namespace TestingControllersSample.Api
{
[Route("api/ideas")]
public class IdeasController : Controller
{
private readonly IBrainstormSessionRepository _sessionRepository;
[HttpGet("forsession/{sessionId}")]
public async Task<IActionResult> ForSession(int sessionId)
{
var session = await _sessionRepository.GetByIdAsync(sessionId);
if (session == null)
{
return NotFound(sessionId);
}
return Ok(result);
}
[HttpPost("create")]
public async Task<IActionResult> Create([FromBody]NewIdeaModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
await _sessionRepository.UpdateAsync(session);
return Ok(session);
}
}
}
El método ForSession devuelve una lista de tipos de IdeaDTO . Evite devolver entidades de dominio de empresa
directamente a través de llamadas API, ya que con frecuencia incluyen más datos de los que el cliente de API
requiere y asocian innecesariamente el modelo de dominio interno de su aplicación con la API que se expone
externamente. La asignación entre las entidades de dominio y los tipos que se van a devolver se puede realizar
manualmente (usando un método Select de LINQ, tal y como se muestra aquí) o por medio de una biblioteca
como AutoMapper.
Estas son las pruebas unitarias de los métodos API Create y ForSession :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Moq;
using TestingControllersSample.Api;
using TestingControllersSample.ClientModels;
using TestingControllersSample.Core.Interfaces;
using TestingControllersSample.Core.Model;
using Xunit;
namespace TestingControllersSample.Tests.UnitTests
{
public class ApiIdeasControllerTests
{
[Fact]
public async Task Create_ReturnsBadRequest_GivenInvalidModel()
{
// Arrange & Act
var mockRepo = new Mock<IBrainstormSessionRepository>();
var controller = new IdeasController(mockRepo.Object);
controller.ModelState.AddModelError("error","some error");
// Act
var result = await controller.Create(model: null);
// Assert
Assert.IsType<BadRequestObjectResult>(result);
}
[Fact]
public async Task Create_ReturnsHttpNotFound_ForInvalidSession()
{
// Arrange
int testSessionId = 123;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.Returns(Task.FromResult((BrainstormSession)null));
var controller = new IdeasController(mockRepo.Object);
// Act
var result = await controller.Create(new NewIdeaModel());
// Assert
Assert.IsType<NotFoundObjectResult>(result);
}
[Fact]
public async Task Create_ReturnsNewlyCreatedIdeaForSession()
{
// Arrange
int testSessionId = 123;
string testName = "test name";
string testDescription = "test description";
var testSession = GetTestSession();
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.Returns(Task.FromResult(testSession));
var controller = new IdeasController(mockRepo.Object);
// Act
var result = await controller.Create(newIdea);
// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
var returnSession = Assert.IsType<BrainstormSession>(okResult.Value);
mockRepo.Verify();
Assert.Equal(2, returnSession.Ideas.Count());
Assert.Equal(testName, returnSession.Ideas.LastOrDefault().Name);
Assert.Equal(testDescription, returnSession.Ideas.LastOrDefault().Description);
}
Como se ha indicado anteriormente, si quiere comprobar el comportamiento del método cuando ModelState no es
válido, agregue un error de modelo al controlador como parte de la prueba. No intente probar la validación del
modelo o el enlace de modelos en las pruebas unitarias: céntrese tan solo en el comportamiento de su método de
acción al confrontarlo con un valor de ModelState determinado.
La segunda prueba depende de que el repositorio devuelva null, por lo que el repositorio ficticio está configurado
para devolver un valor null. No es necesario crear una base de datos de prueba (en memoria o de cualquier otro
modo) ni crear una consulta que devuelva este resultado. Esto se puede realizar en una sola instrucción, tal y como
se muestra.
La última prueba confirma que se llama al método Update del repositorio. Tal y como hicimos anteriormente, se
llama al objeto ficticio con Verifiable y, después, se llama al método Verify del repositorio ficticio para confirmar
que el método Verifiable se ha ejecutado. Las pruebas unitarias no se encargan de garantizar que el método
Update guarda los datos; esto se puede realizar con una prueba de integración.
Pruebas de integración
Las pruebas de integración se realizan para garantizar que distintos módulos independientes dentro de la
aplicación funcionan correctamente juntos. Por lo general, todo lo que se puede comprobar con una prueba unitaria
también se puede comprobar con una prueba de integración, pero no a la inversa. Pero las pruebas de integración
suelen ser mucho más lentas que las unitarias. Por tanto, lo mejor es comprobar todo lo que sea factible con las
pruebas unitarias y recurrir a las pruebas de integración en los casos en los que existan varios colaboradores.
Los objetos ficticios rara vez se usan en las pruebas de integración, aunque pueden seguir siendo de utilidad. En las
pruebas unitarias, los objetos ficticios constituyen un método eficaz de controlar el modo en que los colaboradores
fuera de la unidad que se está probando deben comportarse según los propósitos de la prueba. En una prueba de
integración se usan colaboradores reales para confirmar que todo el subsistema funciona correctamente en
conjunto.
Estado de la aplicación
Una consideración importante al realizar pruebas de integración es cómo establecer el estado de la aplicación. Las
pruebas se deben ejecutar de manera independiente entre sí, por lo que cada prueba debe comenzar con la
aplicación en un estado conocido. Que la aplicación no use una base de datos o tenga algún tipo de persistencia no
debe ser un problema. Pero la mayoría de las aplicaciones reales almacenan su estado en algún tipo de almacén de
datos, de modo que cualquier modificación que se realice a raíz de una prueba puede repercutir en otra, a menos
que se restablezca el almacén de datos. Si se usa el método integrado TestServer , será muy fácil hospedar
aplicaciones ASP.NET Core en nuestras pruebas de integración, pero esto no da acceso necesariamente a los datos
que se van a usar. Si se usa una base de datos real, un método consiste en conectar la aplicación a una base de
datos de prueba, a la que las pruebas pueden obtener acceso, y confirmar que está restablecida en un estado
conocido antes de que cada prueba se ejecute.
En esta aplicación de ejemplo, uso la base de datos InMemoryDatabase de Entity Framework Core, por lo que no
puedo simplemente conectarme a ella desde mi proyecto de prueba. En su lugar, expondré un método
InitializeDatabase desde la clase Startup de la aplicación, y llamaré a ese método cuando la aplicación se inicie si
está en el entorno Development . Mis pruebas de integración sacarán partido de esto automáticamente siempre que
tengan el entorno establecido en Development . No tiene que preocuparse de restablecer la base de datos, ya que
InMemoryDatabase se restablece cada vez que la aplicación se reinicia.
La clase Startup :
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using TestingControllersSample.Core.Interfaces;
using TestingControllersSample.Core.Model;
using TestingControllersSample.Infrastructure;
namespace TestingControllersSample
{
public class Startup
{
{
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>(
optionsBuilder => optionsBuilder.UseInMemoryDatabase("InMemoryDb"));
services.AddMvc();
services.AddScoped<IBrainstormSessionRepository,
EFStormSessionRepository>();
}
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
Verá que el método GetTestSession se usa con bastante asiduidad en las siguientes pruebas de integración.
Acceso a las vistas
En cada clase de prueba de integración se configura el método TestServer que ejecutará la aplicación de ASP.NET
Core. TestServer hospeda la aplicación web de forma predeterminada en la carpeta donde se está ejecutando (en
este caso, la carpeta del proyecto de prueba). Por tanto, si intenta probar las acciones del controlador que devuelven
ViewResult , es posible que aparezca este error:
The view 'Index' wasn't found. The following locations were searched:
(list of locations)
Para corregir este problema, debe configurar la raíz del contenido del servidor de forma que pueda localizar las
vistas del proyecto que se está comprobando. Esto se consigue con una llamada a UseContentRoot en la clase
TestFixture , como se aprecia aquí:
using System;
using System.IO;
using System.Net.Http;
using System.Reflection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.ViewComponents;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
namespace TestingControllersSample.Tests.IntegrationTests
{
/// <summary>
/// A test fixture which hosts the target project (project we wish to test) in an in-memory server.
/// </summary>
/// <typeparam name="TStartup">Target project's startup type</typeparam>
public class TestFixture<TStartup> : IDisposable
{
private readonly TestServer _server;
public TestFixture()
: this(Path.Combine("src"))
{
}
Client = _server.CreateClient();
Client.BaseAddress = new Uri("http://localhost");
}
services.AddSingleton(manager);
}
/// <summary>
/// Gets the full path to the target project that we wish to test
/// </summary>
/// <param name="projectRelativePath">
/// The parent directory of the target project.
/// e.g. src, samples, test, or test/Websites
/// </param>
/// <param name="startupAssembly">The target project's assembly.</param>
/// <returns>The full path to the target project.</returns>
private static string GetProjectPath(string projectRelativePath, Assembly startupAssembly)
{
// Get name of the target project which we want to test
var projectName = startupAssembly.GetName().Name;
throw new Exception($"Project root could not be located using the application root
{applicationBasePath}.");
}
}
}
La clase TestFixture se encarga de configurar y crear el método TestServer , que configura un HttpClient para
comunicarse con dicho método TestServer . En cada una de las pruebas de integración se usa la propiedad Client
para conectarse al servidor de prueba y realizar una solicitud.
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Xunit;
namespace TestingControllersSample.Tests.IntegrationTests
{
public class HomeControllerTests : IClassFixture<TestFixture<TestingControllersSample.Startup>>
{
private readonly HttpClient _client;
[Fact]
public async Task ReturnsInitialListOfBrainstormSessions()
{
// Arrange - get a session known to exist
var testSession = Startup.GetTestSession();
// Act
var response = await _client.GetAsync("/");
// Assert
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
Assert.Contains(testSession.Name, responseString);
}
[Fact]
public async Task PostAddsNewBrainstormSession()
{
// Arrange
string testSessionName = Guid.NewGuid().ToString();
var data = new Dictionary<string, string>();
data.Add("SessionName", testSessionName);
var content = new FormUrlEncodedContent(data);
// Act
var response = await _client.PostAsync("/", content);
// Assert
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.Equal("/", response.Headers.Location.ToString());
}
}
}
En la primera prueba de arriba, responseString contiene el HTML realmente presentado de la vista, que se puede
revisar para confirmar que contiene los resultados esperados.
La segunda prueba crea un formulario POST con un nombre de sesión único y lo envía a la aplicación para,
seguidamente, confirmar que se devuelve la redirección prevista.
Métodos de API
Si la aplicación expone API web, conviene confirmar que se ejecutan según lo previsto por medio de pruebas
automatizadas. El método integrado TestServer permite comprobar API web de forma muy sencilla. Si los
métodos de API usan el enlace de modelos, deberá comprobar siempre el factor ModelState.IsValid y, en este
sentido, las pruebas de integración son el lugar adecuado para confirmar que la validación del modelo funciona
correctamente.
El siguiente conjunto de pruebas tiene como destino el método Create en la clase IdeasController de arriba:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
using TestingControllersSample.ClientModels;
using TestingControllersSample.Core.Model;
using Xunit;
namespace TestingControllersSample.Tests.IntegrationTests
{
public class ApiIdeasControllerTests : IClassFixture<TestFixture<TestingControllersSample.Startup>>
{
internal class NewIdeaDto
{
public NewIdeaDto(string name, string description, int sessionId)
{
Name = name;
Description = description;
SessionId = sessionId;
}
[Fact]
public async Task CreatePostReturnsBadRequestForMissingNameValue()
{
// Arrange
var newIdea = new NewIdeaDto("", "Description", 1);
// Act
var response = await _client.PostAsJsonAsync("/api/ideas/create", newIdea);
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
public async Task CreatePostReturnsBadRequestForMissingDescriptionValue()
{
// Arrange
var newIdea = new NewIdeaDto("Name", "", 1);
// Act
var response = await _client.PostAsJsonAsync("/api/ideas/create", newIdea);
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
public async Task CreatePostReturnsBadRequestForSessionIdValueTooSmall()
{
// Arrange
// Arrange
var newIdea = new NewIdeaDto("Name", "Description", 0);
// Act
var response = await _client.PostAsJsonAsync("/api/ideas/create", newIdea);
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
public async Task CreatePostReturnsBadRequestForSessionIdValueTooLarge()
{
// Arrange
var newIdea = new NewIdeaDto("Name", "Description", 1000001);
// Act
var response = await _client.PostAsJsonAsync("/api/ideas/create", newIdea);
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
public async Task CreatePostReturnsNotFoundForInvalidSession()
{
// Arrange
var newIdea = new NewIdeaDto("Name", "Description", 123);
// Act
var response = await _client.PostAsJsonAsync("/api/ideas/create", newIdea);
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task CreatePostReturnsCreatedIdeaWithCorrectInputs()
{
// Arrange
var testIdeaName = Guid.NewGuid().ToString();
var newIdea = new NewIdeaDto(testIdeaName, "Description", 1);
// Act
var response = await _client.PostAsJsonAsync("/api/ideas/create", newIdea);
// Assert
response.EnsureSuccessStatusCode();
var returnedSession = await response.Content.ReadAsJsonAsync<BrainstormSession>();
Assert.Equal(2, returnedSession.Ideas.Count);
Assert.Contains(testIdeaName, returnedSession.Ideas.Select(i => i.Name).ToList());
}
[Fact]
public async Task ForSessionReturnsNotFoundForBadSessionId()
{
// Arrange & Act
var response = await _client.GetAsync("/api/ideas/forsession/500");
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task ForSessionReturnsIdeasForValidSessionId()
{
// Arrange
var testSession = Startup.GetTestSession();
// Act
// Act
var response = await _client.GetAsync("/api/ideas/forsession/1");
// Assert
response.EnsureSuccessStatusCode();
var ideaList = JsonConvert.DeserializeObject<List<IdeaDTO>>(
await response.Content.ReadAsStringAsync());
var firstIdea = ideaList.First();
Assert.Equal(testSession.Ideas.First().Name, firstIdea.Name);
}
}
}
A diferencia de las pruebas de integración de acciones que devuelven vistas HTML, los métodos de API web que
devuelven resultados se suelen poder deserializar como objetos fuertemente tipados, tal y como arroja la última
prueba mostrada arriba. En este caso, la prueba deserializa el resultado en una instancia de BrainstormSession y
confirma que la idea se agregó correctamente a la colección de ideas.
En el proyecto de ejemplo de este artículo encontrará más ejemplos de pruebas de integración.
Temas avanzados de ASP.NET Core MVC
21/06/2018 • 2 minutes to read • Edit Online
Modelos y proveedores
El modelo de aplicación de ASP.NET Core MVC incluye interfaces abstractas y clases de implementación concretas
que describen una aplicación MVC. Este modelo es el resultado de la detección por parte de MVC de los
controladores, las acciones, los parámetros de acción, las rutas y los filtros de la aplicación de acuerdo con las
convenciones predeterminadas. Cuando trabaje con el modelo de aplicación, puede modificar la aplicación para
que siga convenciones diferentes del comportamiento predeterminado de MVC. Los parámetros, nombres, rutas y
filtros se usan como datos de configuración para las acciones y los controladores.
El modelo de aplicación de ASP.NET Core MVC tiene la estructura siguiente:
ApplicationModel
Controladores (ControllerModel)
Acciones (ActionModel)
Parámetros (ParameterModel)
Cada nivel del modelo tiene acceso a una colección Properties común, y los niveles inferiores pueden tener
acceso a los valores de propiedad establecidos por los niveles superiores de la jerarquía y sobrescribirlos. Las
propiedades se conservan en ActionDescriptor.Properties cuando se crean las acciones. Después, cuando se
controla una solicitud, se puede obtener acceso a través de ActionContext.ActionDescriptor.Properties a todas las
propiedades que agregue o modifique una convención. El uso de propiedades es una manera excelente de
configurar por acción los filtros, los enlazadores de modelos, etc.
NOTE
La colección ActionDescriptor.Properties no es segura para subprocesos (para escrituras) una vez que el inicio de la
aplicación haya finalizado. Las convenciones son la mejor manera de agregar datos de forma segura a esta colección.
IApplicationModelProvider
ASP.NET Core MVC carga el modelo de aplicación mediante un patrón de proveedor definido por la interfaz
IApplicationModelProvider. En esta sección se describen algunos detalles de implementación interna relacionados
con el funcionamiento de este proveedor. Se trata de un tema avanzado, ya que la mayoría de las aplicaciones que
aprovechan el modelo de aplicación deberían hacerlo mediante convenciones.
Las implementaciones de la interfaz IApplicationModelProvider "se encapsulan" entre sí, y cada implementación
llama a OnProvidersExecuting en orden ascendente en función de su propiedad Order . Después, se llama al
método OnProvidersExecuted en orden inverso. El marco de trabajo define varios proveedores:
Primero, ( Order=-1000 ):
DefaultApplicationModelProvider
Después, ( Order=-990 ):
AuthorizationApplicationModelProvider
CorsApplicationModelProvider
NOTE
El orden en que se llama a dos proveedores con el mismo valor para Order no está definido y, por tanto, no se debe confiar
en él.
NOTE
IApplicationModelProvider es un concepto avanzado pensado para que los autores del marco de trabajo lo extiendan. En
general, las aplicaciones deben usar convenciones y los marcos de trabajo deben usar proveedores. La diferencia clave es que
los proveedores siempre se ejecutan antes que las convenciones.
Convenciones
El modelo de aplicación define abstracciones de convenciones que proporcionan una forma más sencilla de
personalizar el comportamiento de los modelos que invalidan el modelo completo o el proveedor. Se recomienda
el uso de estas abstracciones para modificar el comportamiento de la aplicación. Las convenciones ofrecen una
manera de escribir código que aplica dinámicamente las personalizaciones. Mientras los filtros proporcionan una
forma de modificar el comportamiento del marco de trabajo, las personalizaciones permiten controlar cómo se
conecta la aplicación en su conjunto.
Están disponibles las convenciones siguientes:
IApplicationModelConvention
IControllerModelConvention
IActionModelConvention
IParameterModelConvention
Para aplicar las convenciones, se agregan a las opciones de MVC, o bien se implementan valores de tipo
Attribute y se aplican a controladores, acciones o parámetros de acción (de forma similar al uso de Filters ). A
diferencia de los filtros, las convenciones solo se ejecutan cuando se inicia la aplicación, no como parte de cada
solicitud.
Ejemplo: modificar ApplicationModel
La siguiente convención se usa para agregar una propiedad al modelo de aplicación.
using Microsoft.AspNetCore.Mvc.ApplicationModels;
namespace AppModelSample.Conventions
{
public class ApplicationDescription : IApplicationModelConvention
{
private readonly string _description;
Las convenciones del modelo de aplicación se aplican como opciones cuando se agrega MVC en
ConfigureServices en Startup .
Las propiedades son accesibles desde la colección de propiedades ActionDescriptor dentro de las acciones de
controlador:
using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
namespace AppModelSample.Conventions
{
public class ControllerDescriptionAttribute : Attribute, IControllerModelConvention
{
private readonly string _description;
[ControllerDescription("Controller Description")]
public class DescriptionAttributesController : Controller
{
public string Index()
{
return "Description: " + ControllerContext.ActionDescriptor.Properties["description"];
}
using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
namespace AppModelSample.Conventions
{
public class ActionDescriptionAttribute : Attribute, IActionModelConvention
{
private readonly string _description;
Si se aplica a una acción dentro del controlador del ejemplo anterior se puede ver cómo invalida la convención en
el nivel de controlador:
[ControllerDescription("Controller Description")]
public class DescriptionAttributesController : Controller
{
public string Index()
{
return "Description: " + ControllerContext.ActionDescriptor.Properties["description"];
}
[ActionDescription("Action Description")]
public string UseActionDescriptionAttribute()
{
return "Description: " + ControllerContext.ActionDescriptor.Properties["description"];
}
}
using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace AppModelSample.Conventions
{
public class MustBeInRouteParameterModelConvention : Attribute, IParameterModelConvention
{
public void Apply(ParameterModel model)
{
if (model.BindingInfo == null)
{
model.BindingInfo = new BindingInfo();
}
model.BindingInfo.BindingSource = BindingSource.Path;
}
}
}
namespace AppModelSample.Conventions
{
public class CustomActionNameAttribute : Attribute, IActionModelConvention
{
private readonly string _actionName;
// Route: /Home/MyCoolAction
[CustomActionName("MyCoolAction")]
public string SomeName()
{
return ControllerContext.ActionDescriptor.ActionName;
}
Aunque el nombre del método es SomeName , el atributo invalida la convención de MVC de usar el nombre del
método y reemplaza el nombre de la acción por MyCoolAction . De este modo, la ruta usada para llegar a esta
acción es /Home/MyCoolAction .
NOTE
Este ejemplo básicamente equivale a usar el atributo ActionName integrado.
namespace AppModelSample.Conventions
{
public class NamespaceRoutingConvention : IApplicationModelConvention
{
public void Apply(ApplicationModel application)
{
foreach (var controller in application.Controllers)
{
var hasAttributeRouteModels = controller.Selectors
.Any(selector => selector.AttributeRouteModel != null);
if (!hasAttributeRouteModels
&& controller.ControllerName.Contains("Namespace")) // affect one controller in this
sample
{
// Replace the . in the namespace with a / to create the attribute route
// Ex: MySite.Admin namespace will correspond to MySite/Admin attribute route
// Then attach [controller], [action] and optional {id?} token.
// [Controller] and [action] is replaced with the controller and action
// name to generate the final template
controller.Selectors[0].AttributeRouteModel = new AttributeRouteModel()
{
Template = controller.ControllerType.Namespace.Replace('.', '/') +
"/[controller]/[action]/{id?}"
};
}
}
// You can continue to put attribute route templates for the controller actions depending on the
way you want them to behave
}
}
}
TIP
Para agregar convenciones a su software intermedio, obtenga acceso a MvcOptions con
services.Configure<MvcOptions>(c => c.Conventions.Add(YOURCONVENTION)); .
En este ejemplo se aplica esta convención a las rutas que no usan el enrutamiento de atributos donde el
controlador contiene "Namespace" en su nombre. En el controlador siguiente se muestra esta convención:
using Microsoft.AspNetCore.Mvc;
namespace AppModelSample.Controllers
{
public class NamespaceRoutingController : Controller
{
// using NamespaceRoutingConvention
// route: /AppModelSample/Controllers/NamespaceRouting/Index
public string Index()
{
return "This demonstrates namespace routing.";
}
}
}
NOTE
Obtenga más información sobre la migración desde ASP.NET Web API.
Para usar las correcciones de compatibilidad (shim) de Web API, debe agregar el paquete al proyecto y, después,
agregar las convenciones a MVC mediante una llamada a AddWebApiConventions en Startup :
services.AddMvc().AddWebApiConventions();
Las convenciones proporcionadas por las correcciones de compatibilidad solo se aplican a las partes de la
aplicación a las que se han aplicado ciertos atributos. Los cuatro atributos siguientes se usan para controlar en qué
controladores se deben modificar las convenciones mediante las convenciones de las correcciones de
compatibilidad:
UseWebApiActionConventionsAttribute
UseWebApiOverloadingAttribute
UseWebApiParameterConventionsAttribute
UseWebApiRoutesAttribute
Convenciones de acción
UseWebApiActionConventionsAttribute se usa para asignar el método HTTP a las acciones según su nombre (por
ejemplo, Get )
se asignaría a HttpGet . Solo se aplica a las acciones que no usan el enrutamiento de atributos.
Sobrecarga
UseWebApiOverloadingAttribute se usa para aplicar la convención WebApiOverloadingApplicationModelConvention .
Esta convención agrega OverloadActionConstraint al proceso de selección de acciones, que limita las acciones
candidatas a aquellas en las que la solicitud cumple todos los parámetros no opcionales.
Convenciones de parámetro
UseWebApiParameterConventionsAttribute se usa para aplicar la convención de acción
WebApiParameterConventionsApplicationModelConvention . Esta convención especifica que los tipos simples usados
como parámetros de acción se enlazan desde el URI de forma predeterminada, mientras que los tipos complejos
se enlazan desde el cuerpo de la solicitud.
Rutas
UseWebApiRoutesAttribute controla si se ha aplicado la convención de controlador
WebApiApplicationModelConvention . Cuando se habilita, esta convención se usa para agregar a la ruta
compatibilidad con áreas.
Además de un conjunto de convenciones, el paquete de compatibilidad incluye una clase base
System.Web.Http.ApiController que reemplaza la que proporciona Web API. Esto permite que los controladores
escritos para Web API y que heredan de ApiController funcionen de conformidad con su diseño, mientras se
ejecutan en ASP.NET Core MVC. Esta clase de controlador base se decora con todos los atributos UseWebApi*
mencionados anteriormente. ApiController expone propiedades, métodos y tipos de resultados compatibles con
los que se encuentran en Web API.
using Microsoft.AspNetCore.Mvc.ApplicationModels;
namespace AppModelSample.Conventions
{
public class EnableApiExplorerApplicationConvention : IApplicationModelConvention
{
public void Apply(ApplicationModel application)
{
application.ApiExplorer.IsVisible = true;
}
}
}
Mediante el uso de este enfoque (y de convenciones adicionales si es necesario), puede habilitar o deshabilitar las
visibilidad de API en cualquier nivel dentro de la aplicación.
Filtros en ASP.NET Core
31/05/2018 • 38 minutes to read • Edit Online
IMPORTANT
Este tema no es válido con páginas de Razor. ASP.NET Core 2.1 y versiones posteriores admiten IPageFilter e
IAsyncPageFilter para las páginas de Razor. Para más información, vea Filter methods for Razor Pages (Métodos de
filtrado para páginas de Razor).
using FiltersSample.Helper;
using Microsoft.AspNetCore.Mvc.Filters;
namespace FiltersSample.Filters
{
public class SampleActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// do something before the action executes
}
Los filtros asincrónicos definen un solo método OnStageExecutionAsync. Este método toma un delegado
FilterTypeExecutionDelegate que ejecuta la fase de canalización del filtro. Por ejemplo,
ActionExecutionDelegate llama al método de acción o al siguiente filtro de acción y el usuario puede ejecutar
código antes y después de esta llamada.
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Filters;
namespace FiltersSample.Filters
{
public class SampleAsyncActionFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next)
{
// do something before the action executes
var resultContext = await next();
// do something after the action executes; resultContext.Result will be set
}
}
}
Se pueden implementar interfaces que abarquen varias fases de filtro en una sola clase. Por ejemplo, la clase
ActionFilterAttribute implementa IActionFilter e IResultFilter , así como sus equivalentes asincrónicos.
NOTE
Implemente la versión sincrónica o la versión asincrónica de una interfaz de filtro, pero no ambas. El marco
comprueba primero si el filtro implementa la interfaz asincrónica y, si es así, es a la interfaz que llama. De lo contrario,
llamará a métodos de interfaz sincrónicos. Si se implementaran ambas interfaces en una clase, solamente se llamaría al
método asincrónico. Cuando se usan clases abstractas como ActionFilterAttribute, se invalidan solo los métodos
sincrónicos o el método asincrónico de cada tipo de filtro.
IFilterFactory
IFilterFactory implementa IFilter . Por tanto, una instancia de IFilterFactory se puede usar como una
instancia de IFilter en cualquier parte de la canalización de filtro. Cuando el marco se prepara para
invocar el filtro, intenta convertirlo a un IFilterFactory . Si esa conversión se realiza correctamente, se llama
al método CreateInstance para crear la instancia IFilter que se va a invocar. Esto proporciona un diseño
flexible, dado que no hay que establecer la canalización de filtro exacta de forma explícita cuando la
aplicación se inicia.
Puede implementar IFilterFactory en sus propias implementaciones de atributo como método alternativo
para crear filtros:
public class AddHeaderWithFactoryAttribute : Attribute, IFilterFactory
{
// Implement IFilterFactory
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return new InternalAddHeaderFilter();
}
using Microsoft.AspNetCore.Mvc.Filters;
namespace FiltersSample.Filters
{
public class AddHeaderAttribute : ResultFilterAttribute
{
private readonly string _name;
private readonly string _value;
Los atributos permiten a los filtros aceptar argumentos, como se muestra en el ejemplo anterior. Podríamos
agregar este atributo a un método de acción o controlador, y especificar el nombre y el valor del encabezado
HTTP:
[AddHeader("Author", "Steve Smith @ardalis")]
public class SampleController : Controller
{
public IActionResult Index()
{
return Content("Examine the headers using developer tools.");
}
[ShortCircuitingResourceFilter]
public IActionResult SomeResource()
{
return Content("Successful access to resource - header should be set.");
}
Aquí se muestra el resultado de la acción Index ; los encabezados de respuesta aparecen en la parte inferior
derecha.
Algunas de las interfaces de filtro tienen atributos correspondientes que se pueden usar como clases base en
las implementaciones personalizadas.
Atributos de filtro:
ActionFilterAttribute
ExceptionFilterAttribute
ResultFilterAttribute
FormatFilterAttribute
ServiceFilterAttribute
TypeFilterAttribute
services.AddScoped<AddHeaderFilterWithDi>();
}
1 Global OnActionExecuting
2 Controlador OnActionExecuting
3 Método OnActionExecuting
4 Método OnActionExecuted
5 Controlador OnActionExecuted
6 Global OnActionExecuted
Si tuviéramos estos tres mismos filtros de acciones del ejemplo anterior, pero estableciéramos la propiedad
Order de los filtros global y del controlador en 1 y 2 respectivamente, el orden de ejecución se invertiría.
1 Método 0 OnActionExecuting
2 Controlador 1 OnActionExecuting
3 Global 2 OnActionExecuting
4 Global 2 OnActionExecuted
5 Controlador 1 OnActionExecuted
6 Método 0 OnActionExecuted
La propiedad Order altera el ámbito al determinar el orden en el que se ejecutarán los filtros. Los filtros se
clasifican por orden en primer lugar y, después, se usa el ámbito para priorizar en caso de igualdad. Todos
los filtros integrados implementan IOrderedFilter y establecen el valor predeterminado de Order en 0. En
los filtros integrados, el ámbito determinará el orden, a menos que Order se establezca en un valor distinto
de cero.
Cancelación y cortocircuito
La canalización de filtro se puede cortocircuitar en cualquier momento estableciendo la propiedad Result
en el parámetro context que se ha proporcionado al método de filtro. Por ejemplo, el siguiente filtro de
recursos impide que el resto de la canalización se ejecute.
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace FiltersSample.Filters
{
public class ShortCircuitingResourceFilterAttribute : Attribute,
IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
context.Result = new ContentResult()
{
Content = "Resource unavailable - header should not be set"
};
}
En el siguiente código, tanto el filtro ShortCircuitingResourceFilter como el filtro AddHeader tienen como
destino el método de acción SomeResource . El ShortCircuitingResourceFilter :
Se ejecuta en primer lugar, porque es un filtro de recursos y AddHeader es un filtro de acciones.
Cortocircuita el resto de la canalización.
Por tanto, el filtro AddHeader nunca se ejecuta en relación con la acción SomeResource . Este comportamiento
sería el mismo si ambos filtros se aplicaran en el nivel de método de acción, siempre y cuando
ShortCircuitingResourceFilter se haya ejecutado primero. ShortCircuitingResourceFilter se ejecuta
primero debido a su tipo de filtro o al uso explícito de la propiedad Order .
[ShortCircuitingResourceFilter]
public IActionResult SomeResource()
{
return Content("Successful access to resource - header should be set.");
}
Inserción de dependencias
Los filtros se pueden agregar por tipo o por instancia. Si agrega una instancia, dicha instancia se usará en
cada solicitud. Si se agrega un tipo, se activará por tipo, lo que significa que se creará una instancia por cada
solicitud y las dependencias de constructor que haya se rellenarán por medio de la inserción de
dependencias. Agregar un filtro por tipo equivale a usar
filters.Add(new TypeFilterAttribute(typeof(MyFilter))) .
Los filtros que se implementan como atributos y se agregan directamente a las clases de controlador o a los
métodos de acción no pueden tener dependencias de constructor proporcionadas por la inserción de
dependencias. El motivo es que los atributos deben tener los parámetros de constructor proporcionados allá
donde se apliquen. Se trata de una limitación de cómo funcionan los atributos.
Si los filtros tienen dependencias a las que hay que tener acceso desde la inserción de dependencias, existen
varios métodos posibles para ello. Puede aplicar el filtro a una clase o a un método de acción usando
cualquiera de los siguientes elementos:
ServiceFilterAttribute
TypeFilterAttribute
IFilterFactory implementado en el atributo
NOTE
Una dependencia que probablemente convenga obtener de la inserción de dependencias es un registrador. Pese a ello,
no cree ni use filtros con fines exclusivamente de registro, ya que seguro que las características de registro del marco
integradas ya proporcionan lo que necesita. Si va a agregar funciones de registro a los filtros, estas deberán ir dirigidas
a cuestiones de dominio empresarial o a un comportamiento específico del filtro, y no a acciones de MVC o a otros
eventos del marco.
ServiceFilterAttribute
ServiceFilter recupera una instancia del filtro de la inserción de dependencias. Para ello, se agrega el filtro
en cuestión al contenedor de ConfigureServices y se hace referencia a él en un atributo ServiceFilter .
services.AddScoped<AddHeaderFilterWithDi>();
}
[ServiceFilter(typeof(AddHeaderFilterWithDi))]
public IActionResult Index()
{
return View();
}
[TypeFilter(typeof(AddHeaderAttribute),
Arguments = new object[] { "Author", "Steve Smith (@ardalis)" })]
public IActionResult Hi(string name)
{
return Content($"Hi {name}");
}
Este filtro se puede aplicar a clases o a métodos usando la sintaxis de [SampleActionFilter] , en lugar de
tener que recurrir a [TypeFilter] o a [ServiceFilter] .
Filtros de autorización
Los *filtros de autorización:
Controlan el acceso a los métodos de acción.
Son los primeros filtros que se ejecutan en la canalización de filtro.
Tienen un método anterior, pero no uno posterior.
Solo se deben escribir filtros de autorización personalizados si se está escribiendo un marco de autorización
propio. Es preferible configurar directivas de autorización o escribir una directiva de autorización
personalizada a escribir un filtro personalizado. La implementación de filtro integrada se encarga
únicamente de llamar al sistema de autorización.
No se deberían producir excepciones dentro de los filtros de autorización, ya que no habrá nada que
controle esas excepciones (los filtros de excepciones no lo harán). Considere la posibilidad de emitir un
desafío cuando se produzca una excepción.
Aquí encontrará más información sobre la autorización.
Filtros de recursos
Implementan la interfaz IResourceFilter o IAsyncResourceFilter .
Su ejecución abarca la mayor parte de la canalización de filtro.
Los filtros de autorización son los únicos que se ejecutan antes que los filtros de recursos.
Los filtros de recursos son útiles para cortocircuitar la mayor parte del trabajo que está realizando una
solicitud. Por ejemplo, un filtro de almacenamiento en caché puede evitar que se ejecute el resto de la
canalización si la respuesta está en la memoria caché.
El filtro de recursos de cortocircuito mostrado anteriormente es un ejemplo de filtro de recursos. Otro
ejemplo es DisableFormValueModelBindingAttribute:
Evita que el enlace de modelos tenga acceso a los datos del formulario.
Resulta práctico cuando hay cargas de archivos muy voluminosos y se quiere impedir que el formulario
se lea en la memoria.
Filtros de acciones
Los filtros de acciones:
Implementan la interfaz IActionFilter o IAsyncActionFilter .
Su ejecución rodea la ejecución de los métodos de acción.
Este es un filtro de acciones de ejemplo:
El marco proporciona una clase abstracta ActionFilterAttribute de la que se pueden crear subclases.
Un filtro de acciones puede servir para validar el estado del modelo y devolver cualquier error que surja si el
estado no es válido:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace FiltersSample.Filters
{
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
}
}
El método OnActionExecuted se ejecuta después del método de acción y puede ver y manipular los
resultados de la acción a través de la propiedad ActionExecutedContext.Result .
ActionExecutedContext.Canceled se establecerá en true si otro filtro ha cortocircuitado la ejecución de la
acción. ActionExecutedContext.Exception se establecerá en un valor distinto de null si la acción o un filtro de
acción posterior han producido una excepción. Si ActionExecutedContext.Exception se establece como nulo:
Controla una excepción eficazmente.
ActionExectedContext.Result se ejecuta como si se devolviera con normalidad desde el método de
acción.
Filtros de excepciones
Los filtros de excepciones implementan la interfaz IExceptionFilter o IAsyncExceptionFilter . Se pueden
usar para implementar directivas de control de errores comunes de una aplicación.
En el siguiente filtro de excepciones de ejemplo se usa una vista de error de desarrollador personalizada
para mostrar los detalles sobre las excepciones que se producen cuando la aplicación está en fase de
desarrollo:
public CustomExceptionFilterAttribute(
IHostingEnvironment hostingEnvironment,
IModelMetadataProvider modelMetadataProvider)
{
_hostingEnvironment = hostingEnvironment;
_modelMetadataProvider = modelMetadataProvider;
}
NOTE
En ASP.NET Core 1.1, la respuesta no se envía si ExceptionHandled está establecido en true y escribe una respuesta.
En este caso, ASP.NET Core 1.0 envía la respuesta, mientras que ASP.NET Core 1.1.2 regresará al comportamiento de la
versión 1.0. Para más información, vea el problema n.º 5594 del repositorio de GitHub.
Filtros de resultados
Implementan la interfaz IResultFilter o IAsyncResultFilter .
Su ejecución rodea la ejecución de los resultados de acción.
Este es un ejemplo de un filtro de resultados que agrega un encabezado HTTP.
El tipo de resultado que se ejecute dependerá de la acción en cuestión. Una acción de MVC que devuelve
una vista incluye todo el procesamiento de Razor como parte del elemento ViewResult que se está
ejecutando. Un método API puede llevar a cabo algunas funciones de serialización como parte de la
ejecución del resultado. Aquí encontrará más información sobre los resultados de acciones.
Los filtros de resultados solo se ejecutan cuando los resultados son correctos; es decir, cuando la acción o los
filtros de acciones generan un resultado de acción. Los filtros de resultados no se ejecutan si hay filtros de
excepciones que controlan una excepción.
El método OnResultExecuting puede cortocircuitar la ejecución del resultado de la acción y de los filtros de
resultados posteriores estableciendo ResultExecutingContext.Cancel en true. Por lo general, conviene
escribir en el objeto de respuesta cuando el proceso se cortocircuite, ya que así evitará que se genere una
respuesta vacía. Si se produce una excepción, sucederá lo siguiente:
Se impedirá la ejecución del resultado de la acción y de los filtros subsiguientes.
Se tratará como un error en lugar de como un resultado correcto.
Cuando el método OnResultExecuted se ejecuta, probablemente la respuesta se haya enviado al cliente y ya
no se pueda cambiar (a menos que se produzca una excepción). ResultExecutedContext.Canceled se
establecerá en true si otro filtro ha cortocircuitado la ejecución del resultado de la acción.
ResultExecutedContext.Exception se establecerá en un valor distinto de null si el resultado de la acción o un
filtro de resultado posterior ha producido una excepción. Establecer Exception en un valor null hace que
una excepción se "controle" de forma eficaz y evita que MVC vuelva a producir dicha excepción más
adelante en la canalización. Cuando se controla una excepción en un filtro de resultados, no se pueden
escribir datos en la respuesta. Si el resultado de la acción produce una excepción a mitad de su ejecución y
los encabezados ya se han vaciado en el cliente, no hay ningún mecanismo confiable que permita enviar un
código de error.
En un elemento IAsyncResultFilter , una llamada a await next en ResultExecutionDelegate ejecuta
cualquier filtro de resultados posterior y el resultado de la acción. Para cortocircuitar esto, establezca
ResultExecutingContext.Cancel en true y no llame a ResultExectionDelegate .
El marco proporciona una clase abstracta ResultFilterAttribute de la que se pueden crear subclases. La
clase AddHeaderAttribute mostrada anteriormente es un ejemplo de un atributo de filtro de resultados.
applicationBuilder.UseRequestLocalization(options);
}
}
Después, puede usar MiddlewareFilterAttribute para ejecutar el middleware en relación con una acción o
controlador concretos, o bien de manera global:
[Route("{culture}/[controller]/[action]")]
[MiddlewareFilter(typeof(LocalizationPipeline))]
public IActionResult CultureFromRouteData()
{
return Content($"CurrentCulture:{CultureInfo.CurrentCulture.Name},"
+ $"CurrentUICulture:{CultureInfo.CurrentUICulture.Name}");
}
Los filtros de middleware se ejecutan en la misma fase de la canalización de filtro que los filtros de recursos,
antes del enlace de modelos y después del resto de la canalización.
Siguientes acciones
Para experimentar con los filtros, descargue, pruebe y modifique este ejemplo.
Áreas de ASP.NET Core
25/06/2018 • 9 minutes to read • Edit Online
Las áreas ofrecen una manera de dividir una aplicación web ASP.NET Core MVC de gran tamaño en
agrupaciones funcionales más pequeñas. Un área es en realidad una estructura de MVC dentro de una
aplicación. En un proyecto de MVC, los componentes lógicos como el modelo, el controlador y la vista se
guardan en carpetas diferentes, y MVC usa las convenciones de nomenclatura para crear la relación entre estos
componentes. Para una aplicación grande, puede ser conveniente dividir la aplicación en distintas áreas de
funciones de alto nivel. Por ejemplo, una aplicación de comercio electrónico con varias unidades de negocio,
como la finalización de la compra, la facturación, la búsqueda, etc. Cada una de estas unidades tiene sus propias
vistas, controladores y modelos de componentes lógicos. En este escenario, puede usar las áreas para dividir
físicamente los componentes empresariales del mismo proyecto.
Un área puede definirse como unidades funcionales más pequeñas en un proyecto de ASP.NET Core MVC con
su propio conjunto de modelos, vistas y controladores.
Considere el uso de áreas en un proyecto de MVC en los casos siguientes:
La aplicación está formada por varios componentes funcionales de alto nivel que deben separarse
lógicamente.
Le interesa dividir el proyecto de MVC para que se pueda trabajar en cada área funcional de forma
independiente.
Características de las áreas:
Una aplicación ASP.NET Core MVC puede tener cualquier número de áreas.
Cada área tiene sus propios controladores, modelos y vistas.
Permiten organizar proyectos de MVC de gran tamaño en varios componentes de alto nivel en los que se
puede trabajar de forma independiente.
Admiten varios controladores con el mismo nombre, siempre y cuando tengan áreas diferentes.
Veamos un ejemplo para ilustrar cómo se crean y se usan las áreas. Supongamos que tiene una aplicación de
tienda con dos grupos distintos de controladores y vistas: Productos y Servicios. Una estructura de carpetas
típica para dicha aplicación con áreas de MVC tendría un aspecto similar al siguiente:
Nombre de proyecto
Áreas
Productos
Controladores
HomeController.cs
ManageController.cs
Vistas
Página principal
Index.cshtml
Administrar
Index.cshtml
Servicios
Controladores
HomeController.cs
Vistas
Página principal
Index.cshtml
Cuando MVC intenta representar una vista en un área, de forma predeterminada, busca en las ubicaciones
siguientes:
/Areas/<Area-Name>/Views/<Controller-Name>/<Action-Name>.cshtml
/Areas/<Area-Name>/Views/Shared/<Action-Name>.cshtml
/Views/Shared/<Action-Name>.cshtml
Estas son las ubicaciones predeterminadas que se pueden cambiar mediante AreaViewLocationFormats en
Microsoft.AspNetCore.Mvc.Razor.RazorViewEngineOptions .
services.Configure<RazorViewEngineOptions>(options =>
{
options.AreaViewLocationFormats.Clear();
options.AreaViewLocationFormats.Add("/Categories/{2}/Views/{1}/{0}.cshtml");
options.AreaViewLocationFormats.Add("/Categories/{2}/Views/Shared/{0}.cshtml");
options.AreaViewLocationFormats.Add("/Views/Shared/{0}.cshtml");
});
Hay que tener en cuenta que la estructura de la carpeta Views es la única que se considera importante aquí; el
contenido de las demás carpetas, como Controllers y Models, no importa. Por ejemplo, no hace falta tener una
carpeta Controllers y Models. Esto funciona porque el contenido de Controllers y Models solo es código que se
compila en una .dll, mientras que el contenido de Views no lo es mientras no se realice una solicitud a esa vista.
Una vez que haya definido la jerarquía de carpetas, debe indicar a MVC que cada controlador está asociado a un
área. Para hacerlo, decore el nombre del controlador con el atributo [Area] .
...
namespace MyStore.Areas.Products.Controllers
{
[Area("Products")]
public class HomeController : Controller
{
// GET: /Products/Home/Index
public IActionResult Index()
{
return View();
}
// GET: /Products/Home/Create
public IActionResult Create()
{
return View();
}
}
}
Establezca una definición de ruta que funcione con las áreas recién creadas. En el artículo Enrutamiento a
acciones del controlador se explica en detalle cómo crear definiciones de ruta, incluido el uso de rutas
convencionales frente a rutas de atributo. En este ejemplo usaremos una ruta convencional. Para ello, abra el
archivo Startup.cs y modifíquelo mediante la adición de la definición de ruta con nombre areaRoute que se
indica a continuación.
...
app.UseMvc(routes =>
{
routes.MapRoute(
name: "areaRoute",
template: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
Generación de vínculos
Generación de vínculos desde una acción dentro de un controlador basado en área en otra acción dentro
del mismo controlador.
Supongamos que la ruta de la solicitud actual es /Products/Home/Create .
Sintaxis de HtmlHelper: @Html.ActionLink("Go to Product's Home Page", "Index")
Tenga en cuenta que es necesario proporcionar aquí los valores de área y controlador, dado que ya están
disponibles en el contexto de la solicitud actual. Estos tipos de valores se denominan valores ambient .
Generación de vínculos desde una acción dentro de un controlador basado en área en otra acción en un
controlador diferente.
Supongamos que la ruta de la solicitud actual es /Products/Home/Create .
Sintaxis de HtmlHelper: @Html.ActionLink("Go to Manage Products Home Page", "Index", "Manage")
Sintaxis de TagHelper:
<a asp-controller="Manage" asp-action="Index">Go to Manage Products Home Page</a>
Tenga en cuenta que aquí se usa el valor de ambiente de área, pero el valor de controlador se especifica
explícitamente más arriba.
Generación de vínculos desde una acción dentro de un controlador basado en área en otra acción en un
controlador diferente y un área diferente.
Supongamos que la ruta de la solicitud actual es /Products/Home/Create .
Sintaxis de HtmlHelper:
@Html.ActionLink("Go to Services Home Page", "Index", "Home", new { area = "Services" })
Sintaxis de TagHelper:
<a asp-area="Services" asp-controller="Home" asp-action="Index">Go to Services Home Page</a>
Sintaxis de TagHelper:
<a asp-area="" asp-controller="Manage" asp-action="Index">Go to Manage Products Home Page</a>
Dado que queremos generar vínculos en una acción de controlador que no está basada en un área,
vaciaremos aquí el valor de ambiente para el área.
Publicación de áreas
Todos los archivos *.cshtml y wwwroot/** se publican en la salida cuando se incluye
<Project Sdk="Microsoft.NET.Sdk.Web"> en el archivo .csproj.
Elementos de aplicación en ASP.NET Core
25/06/2018 • 7 minutes to read • Edit Online
// OR
var assembly = typeof(Startup).GetTypeInfo().Assembly;
var part = new AssemblyPart(assembly);
services.AddMvc()
.ConfigureApplicationPartManager(apm => apm.ApplicationParts.Add(part));
De forma predeterminada, MVC examinará el árbol de dependencias y buscará controladores (incluso en otros
ensamblados). Para cargar un ensamblado arbitrario (por ejemplo, desde un complemento al que no se hace
referencia en tiempo de compilación), puede utilizar un elemento de aplicación.
Los elementos de aplicación se pueden usar para evitar tener que buscar controladores en una determinada
ubicación o ensamblado. Modifique la colección ApplicationParts de ApplicationPartManager para controlar qué
elementos (o ensamblados) están disponibles para la aplicación. El orden de las entradas de la colección
ApplicationParts es irrelevante. Es importante configurar totalmente ApplicationPartManager antes de usarlo para
configurar los servicios en el contenedor. Por ejemplo, debe configurar totalmente ApplicationPartManager antes
de invocar a AddControllersAsServices . Si no lo hace, significará que los controladores de elementos de aplicación
que se han agregado después de esa llamada al método no se verán afectados (no se registrarán como servicios),
lo que podría dar lugar a un comportamiento incorrecto de la aplicación.
Si tiene un ensamblado que contiene controladores que no quiere usar, quítelo de ApplicationPartManager :
services.AddMvc()
.ConfigureApplicationPartManager(apm =>
{
var dependentLibrary = apm.ApplicationParts
.FirstOrDefault(part => part.Name == "DependentLibrary");
if (dependentLibrary != null)
{
p.ApplicationParts.Remove(dependentLibrary);
}
})
Además del ensamblado del proyecto y sus ensamblados dependientes, ApplicationPartManager incluirá
elementos de Microsoft.AspNetCore.Mvc.TagHelpers y Microsoft.AspNetCore.Mvc.Razor de forma predeterminada.
services.AddMvc()
.ConfigureApplicationPartManager(apm =>
apm.FeatureProviders.Add(new GenericControllerFeatureProvider()));
De forma predeterminada, los nombres de controlador genérico utilizados para el enrutamiento tendrían el
formato GenericController`1 [Widget] en lugar de Widget. El siguiente atributo se usa para modificar el nombre
para coincidir con el tipo genérico usado por el controlador:
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using System;
namespace AppPartsSample
{
// Used to set the controller name for routing purposes. Without this convention the
// names would be like 'GenericController`1[Widget]' instead of 'Widget'.
//
// Conventions can be applied as attributes or added to MvcOptions.Conventions.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class GenericControllerNameConvention : Attribute, IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
if (controller.ControllerType.GetGenericTypeDefinition() !=
typeof(GenericController<>))
{
// Not a GenericController, ignore.
return;
}
La clase GenericController :
using Microsoft.AspNetCore.Mvc;
namespace AppPartsSample
{
[GenericControllerNameConvention] // Sets the controller name based on typeof(T).Name
public class GenericController<T> : Controller
{
public IActionResult Index()
{
return Content($"Hello from a generic {typeof(T).Name} controller.");
}
}
}
namespace AppPartsSample.Controllers
{
public class FeaturesController : Controller
{
private readonly ApplicationPartManager _partManager;
return View(viewModel);
}
}
}
if (context.Metadata.ModelType == typeof(byte[]))
{
return new ByteArrayModelBinder();
}
return null;
}
Cuando cree su propio enlazador de modelos personalizado, puede implementar su tipo IModelBinderProvider
particular o usar ModelBinderAttribute.
En el siguiente ejemplo se indica cómo usar ByteArrayModelBinder para convertir una cadena codificada con
base64 en un byte[] y guardar el resultado en un archivo:
// POST: api/image
[HttpPost]
public void Post(byte[] file, string filename)
{
string filePath = Path.Combine(_env.ContentRootPath, "wwwroot/images/upload", filename);
if (System.IO.File.Exists(filePath)) return;
System.IO.File.WriteAllBytes(filePath, file);
}
Se puede usar un método API POST en una cadena codificada con base64 con una herramienta como Postman:
Siempre y cuando el enlazador pueda enlazar datos de la solicitud a argumentos o propiedades con el nombre
adecuado, el enlace de modelos se realizará correctamente. En el siguiente ejemplo se muestra cómo usar
ByteArrayModelBinder con un modelo de vista:
[HttpPost("Profile")]
public void SaveProfile(ProfileViewModel model)
{
string filePath = Path.Combine(_env.ContentRootPath, "wwwroot/images/upload", model.FileName);
if (System.IO.File.Exists(model.FileName)) return;
System.IO.File.WriteAllBytes(filePath, model.File);
}
using CustomModelBindingSample.Binders;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace CustomModelBindingSample.Data
{
[ModelBinder(BinderType = typeof(AuthorEntityBinder))]
public class Author
{
public int Id { get; set; }
public string Name { get; set; }
public string GitHub { get; set; }
public string Twitter { get; set; }
public string BlogUrl { get; set; }
}
}
En el código anterior, el atributo ModelBinder especifica el tipo de IModelBinder que se debe emplear para enlazar
parámetros de acción de Author .
AuthorEntityBinder sirve para enlazar un parámetro Author capturando la entidad de un origen de datos por
medio de Entity Framework Core y de un authorId :
public class AuthorEntityBinder : IModelBinder
{
private readonly AppDbContext _db;
public AuthorEntityBinder(AppDbContext db)
{
_db = db;
}
if (valueProviderResult == ValueProviderResult.None)
{
return Task.CompletedTask;
}
bindingContext.ModelState.SetModelValue(modelName,
valueProviderResult);
int id = 0;
if (!int.TryParse(value, out id))
{
// Non-integer arguments result in model state errors
bindingContext.ModelState.TryAddModelError(
bindingContext.ModelName,
"Author Id must be an integer.");
return Task.CompletedTask;
}
El atributo ModelBinder se puede usar para aplicar AuthorEntityBinder a los parámetros que no usan
convenciones predeterminadas:
[HttpGet("{id}")]
public IActionResult GetById([ModelBinder(Name = "id")]Author author)
{
if (author == null)
{
return NotFound();
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
return Ok(author);
}
En este ejemplo, como el nombre del argumento no es el authorId predeterminado, se especifica en el parámetro
por medio del atributo ModelBinder . Observe que tanto el controlador como el método de acción se simplifican,
en contraste con tener que buscar la entidad en el método de acción. La lógica para capturar el autor a través de
Entity Framework Core se traslada al enlazador de modelos. Esto puede reducir enormemente la complejidad
cuando existen varios métodos que se enlazan con el modelo de autor, además de contribuir a seguir el principio
Una vez y solo una (DRY ).
Puede aplicar el atributo ModelBinder a propiedades de modelo individuales (como en un modelo de vista) o a
parámetros del método de acción para especificar un determinado nombre de modelo o enlazador de modelos
que sea exclusivo de ese tipo o acción en particular.
Implementar un ModelBinderProvider
En lugar de aplicar un atributo, puede implementar IModelBinderProvider . Así es como se implementan los
enlazadores de marco integrados. Cuando se especifica el tipo con el que el enlazador funciona, lo que se está
indicando es el tipo de argumento que ese enlazador genera, no la entrada que acepta. El siguiente proveedor de
enlazador funciona con AuthorEntityBinder . Cuando se agrega a la colección de proveedores de MVC, no es
necesario usar el atributo ModelBinder en los parámetros con tipo Author o Author .
using CustomModelBindingSample.Data;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using System;
namespace CustomModelBindingSample.Binders
{
public class AuthorEntityBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(Author))
{
return new BinderTypeModelBinder(typeof(AuthorEntityBinder));
}
return null;
}
}
}
Nota: El código anterior devuelve un BinderTypeModelBinder . BinderTypeModelBinder actúa como una fábrica
de enlazadores de modelos y proporciona la inserción de dependencias. AuthorEntityBinder requiere que la
inserción de dependencias tenga acceso a Entity Framework Core. Use BinderTypeModelBinder si su enlazador
de modelos necesita servicios de inserción de dependencias.
services.AddMvc(options =>
{
// add custom binder to beginning of collection
options.ModelBinderProviders.Insert(0, new AuthorEntityBinderProvider());
});
}
services.AddMvc(options =>
{
// add custom binder to beginning of collection
options.ModelBinderProviders.Insert(0, new AuthorEntityBinderProvider());
});
}
Otro enfoque consiste en crear una clase de controlador base personalizada anotada con el atributo
[ApiController] :
[!code-csharp]
En las secciones siguientes se describen las ventajas de las características que aporta el atributo.
Respuestas HTTP 400 automáticas
Los errores de validación desencadenan automáticamente una respuesta HTTP 400. El código siguiente deja de
ser necesario en las acciones:
[!code-csharp]
Este comportamiento predeterminado se deshabilita con siguiente código en Startup.ConfigureServices:
[!code-csharp]
Inferencia de parámetro de origen de enlace
Un atributo de origen de enlace define la ubicación del valor del parámetro de una acción. Existen los atributos de
origen de enlace siguientes:
ATRIBUTO ORIGEN DE ENLACE
NOTE
No use [FromRoute] si los valores pueden contener %2f (es decir, / ) porque %2f no incluirá el carácter sin escape /
. Use [FromQuery] si el valor puede contener %2f .
Sin el [ApiController] atributo, los atributos de origen de enlace se definen explícitamente. En el ejemplo
siguiente, el atributo [FromQuery] indica que el valor del parámetro discontinuedOnly se proporciona en la
cadena de consulta de la dirección URL de la solicitud:
[!code-csharp]
Las reglas de inferencia se aplican para los orígenes de datos predeterminados de los parámetros de acción.
Estas reglas configuran los orígenes de enlace que aplicaría manualmente a los parámetros de acción. Los
atributos de origen de enlace presentan este comportamiento:
[FromBody] se infiere para los parámetros de tipo complejo. La excepción a esta regla es cualquier tipo
integrado complejo que tenga un significado especial, como IFormCollection o CancellationToken. El código
de inferencia del origen de enlace omite esos tipos especiales. Cuando una acción contiene más de un
parámetro que se especifica explícitamente (a través de [FromBody] ) o se infiere como enlazado desde el
cuerpo de la solicitud, se produce una excepción. Por ejemplo, las firmas de acción siguientes provocan una
excepción:
[!code-csharp]
[FromForm ] se infiere para los parámetros de acción de tipo IFormFile o IFormFileCollection. No se infiere
para los tipos simples o definidos por el usuario.
[FromRoute] se infiere para cualquier nombre de parámetro de acción que coincida con un parámetro de la
plantilla de ruta. Si varias rutas coinciden con un parámetro de acción, cualquier valor de ruta se considera
[FromRoute] .
[FromQuery] se infiere para cualquier otro parámetro de acción.
Recursos adicionales
Tipos de valor devuelto de acción del controlador
Formateadores personalizados
Aplicación de formato a datos de respuesta
Páginas de ayuda mediante Swagger
Enrutamiento a acciones del controlador
Temas avanzados sobre ASP.NET Core Web API
21/06/2018 • 2 minutes to read • Edit Online
Formateadores personalizados
Aplicación de formato a datos de respuesta
Probar, depurar y solucionar problemas en ASP.NET
Core
21/06/2018 • 2 minutes to read • Edit Online
Prueba
Prueba unitaria en .NET Core y .NET Standard
Vea cómo usar la prueba unitaria en proyectos de .NET Core y .NET Standard.
Pruebas de integración
Obtenga información sobre cómo las pruebas de integración garantizan que los componentes de una aplicación,
como la base de datos, el sistema de archivos y la red, funcionen correctamente en la infraestructura.
Pruebas unitarias de páginas de Razor
Descubra cómo crear pruebas unitarias para las aplicaciones de páginas de Razor.
Controladores de pruebas
Obtenga información sobre cómo probar la lógica del controlador en ASP.NET Core con Moq y xUnit.
Depuración
Depuración de código fuente de ASP.NET Core 2.x
Obtenga información sobre cómo depurar orígenes de .NET Core y ASP.NET Core.
Depuración remota
Descubra cómo configurar una aplicación ASP.NET Core en Visual Studio 2017, implementarla en IIS con Azure y
agregar el depurador remoto de Visual Studio.
Depuración de instantáneas
Obtenga información sobre cómo recopilar instantáneas sobre las excepciones más importantes con el fin de
tener los datos necesarios para diagnosticar problemas en la producción.
Solucionar problemas
Solucionar problemas
Conozca y solucione advertencias y errores en proyectos de ASP.NET Core.
Pruebas de integración de ASP.NET Core
22/06/2018 • 30 minutes to read • Edit Online
NOTE
En las discusiones de pruebas de integración, el proyecto probado se suele denominar la sistema sometido a prueba, o
"SUT" para abreviar.
El pruebas unitarias documentación describe cómo configurar un proyecto y probar el ejecutor de pruebas, así
como instrucciones detalladas sobre cómo ejecutar pruebas y recomendaciones sobre cómo para
comprobaciones de nombres y clases de prueba.
NOTE
Al crear un proyecto de prueba para una aplicación, separe las pruebas unitarias de las pruebas de integración en proyectos
diferentes. Esto ayuda a asegurarse de que accidentalmente componentes de infraestructura de pruebas no incluidos en las
pruebas unitarias. La separación de las pruebas de unidades y de integración también permite controlar en qué conjunto de
pruebas se ejecutan.
No hay prácticamente ninguna diferencia entre la configuración de pruebas de las aplicaciones de las páginas de
Razor y las aplicaciones MVC. La única diferencia está en cómo se denominan las pruebas. En una aplicación de
páginas de Razor, suelen denominarse pruebas de los puntos de conexión de la página después de la clase de
modelo de página (por ejemplo, IndexPageTests para probar la integración del componente de la página de
índice). En una aplicación MVC, pruebas normalmente están organizadas por las clases de controlador y los
controladores que se someten a prueba con el nombre (por ejemplo, HomeControllerTests para probar la
integración del componente para el controlador Home).
Implementar clases de prueba un accesorio de clase interfaz ( IClassFixture ) para indicar la clase contiene
pruebas y proporciona instancias de objetos compartidos a través de las pruebas de la clase.
Prueba básica de extremos de la aplicación
La siguiente prueba (clase), BasicTests , usa el WebApplicationFactory para arrancar el SUT y proporcionar un
HttpClient a un método de prueba, Get_EndpointsReturnSuccessAndCorrectContentType . El método comprueba si el
código de estado de respuesta es correcto (códigos de estado en el intervalo de 200-299) y la Content-Type
encabezado es text/html; charset=utf-8 para varias páginas de la aplicación.
CreateClient crea una instancia de HttpClient que sigue redirecciones y controla las cookies automáticamente.
public class BasicTests
: IClassFixture<WebApplicationFactory<RazorPagesProject.Startup>>
{
private readonly WebApplicationFactory<RazorPagesProject.Startup> _factory;
[Theory]
[InlineData("/")]
[InlineData("/Index")]
[InlineData("/About")]
[InlineData("/Privacy")]
[InlineData("/Contact")]
public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
{
// Arrange
var client = _factory.CreateClient();
// Act
var response = await client.GetAsync(url);
// Assert
response.EnsureSuccessStatusCode(); // Status Code 200-299
Assert.Equal("text/html; charset=utf-8",
response.Content.Headers.ContentType.ToString());
}
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizePage("/SecurePage");
});
// Act
var response = await client.GetAsync("/SecurePage");
// Assert
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.StartsWith("http://localhost/Identity/Account/Login",
response.Headers.Location.OriginalString);
}
Personalizar WebApplicationFactory
Configuración del host Web puede crearse independientemente de las clases de prueba mediante la adquisición
de WebApplicationFactory para crear uno o varios generadores personalizados:
1. Heredar de WebApplicationFactory e invalide ConfigureWebHost. El IWebHostBuilder permite la
configuración de la colección de servicio con ConfigureServices:
public class CustomWebApplicationFactory<TStartup>
: WebApplicationFactory<RazorPagesProject.Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
// Create a new service provider.
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
try
{
// Seed the database with test data.
Utilities.InitializeDbForTests(db);
}
catch (Exception ex)
{
logger.LogError(ex, $"An error occurred seeding the " +
"database with test messages. Error: {ex.Message}");
}
}
});
}
}
public IndexPageTests(
CustomWebApplicationFactory<RazorPagesProject.Startup> factory)
{
_client = factory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
_factory = factory;
}
[Fact]
public async Task Post_DeleteAllMessagesHandler_ReturnsRedirectToRoot()
{
// Arrange
var defaultPage = await _client.GetAsync("/");
var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
//Act
var response = await _client.SendAsync(
(IHtmlFormElement)content.QuerySelector("form[id='messages']"),
(IHtmlButtonElement)content.QuerySelector("button[id='deleteAllBtn']"));
// Assert
Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode);
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.Equal("/", response.Headers.Location.OriginalString);
}
Cualquier solicitud POST a la SUT debe cumplir la comprobación de antiforgery que se convierte
automáticamente en la aplicación antiforgery sistema de protección de datos. Para organizar de solicitud POST
de una prueba, la aplicación de prueba debe:
1. Realice una solicitud de la página.
2. Analizar la cookie antiforgery y el token de validación de solicitud de la respuesta.
3. Asegúrese de la solicitud POST con la validación de solicitud y cookie antiforgery símbolo (token) en su lugar.
El SendAsync métodos de extensión de aplicación auxiliar (Helpers/HttpClientExtensions.cs) y la GetDocumentAsync
método auxiliar (Helpers/HtmlHelpers.cs) en el deaplicacióndeejemplo usar la AngleSharp analizador para
controlar la comprobación de antiforgery con los métodos siguientes:
GetDocumentAsync – Recibe la HttpResponseMessage y devuelve un IHtmlDocument . GetDocumentAsync usa un
generador que prepara un respuesta virtual basado en el original HttpResponseMessage . Para obtener más
información, consulte el AngleSharp documentación.
SendAsync métodos de extensión para la HttpClient redactar una HttpRequestMessage y llame a
SendAsync(HttpRequestMessage) para enviar solicitudes a la SUT. Sobrecargas de SendAsync acepte el
formulario HTML ( IHtmlFormElement ) y los siguientes:
Botón del formulario de envío ( IHtmlElement )
Colección de valores de formulario ( IEnumerable<KeyValuePair<string, string>> )
Botón de envío ( IHtmlElement ) y valores del formulario ( IEnumerable<KeyValuePair<string, string>> )
NOTE
AngleSharp se utiliza con fines de demostración, en este tema y la aplicación de ejemplo de biblioteca de análisis de un
tercero. AngleSharp no es compatible o requerida para las pruebas de integración de aplicaciones de ASP.NET Core. Pueden
utilizar otros analizadores, como el Html agilidad Pack (GRACIA). Otro enfoque es escribir código para controlar el sistema
antiforgery token de comprobación de solicitud y cookie antiforgery directamente.
try
{
Utilities.InitializeDbForTests(db);
}
catch (Exception ex)
{
logger.LogError(ex, "An error occurred seeding " +
"the database with test messages. Error: " +
ex.Message);
}
}
});
})
.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
var defaultPage = await client.GetAsync("/");
var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
//Act
var response = await client.SendAsync(
(IHtmlFormElement)content.QuerySelector("form[id='messages']"),
(IHtmlButtonElement)content.QuerySelector("button[id='deleteBtn1']"));
// Assert
Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode);
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.Equal("/", response.Headers.Location.OriginalString);
}
Opciones de cliente
En la tabla siguiente se muestra el valor predeterminado WebApplicationFactoryClientOptions disponibles al
crear HttpClient instancias.
_client = _factory.CreateClient(clientOptions);
...
});
}
...
});
}
}
{
"shadowCopy": false
}
Se pueden ejecutar las pruebas con las características integradas de prueba de un IDE, como Visual Studio. Si usa
código de Visual Studio o la línea de comandos, ejecute el siguiente comando en un símbolo del sistema en el
tests/RazorPagesProject.Tests carpeta:
dotnet test
†El tema EF prueba con InMemory, explica cómo utilizar una base de datos en memoria para las pruebas con
MSTest. Este tema se usa el xUnit marco de pruebas. Las implementaciones de prueba a través de los marcos de
pruebas diferente y conceptos de prueba son similares pero no idénticos.
Aunque la aplicación no usa el modelo de repositorio y no es un ejemplo efectivo de la patrón de la unidad de
trabajo (UoW ), las páginas de Razor admite estos patrones de desarrollo. Para obtener más información, consulte
diseñar la capa de persistencia de infraestructura, implementación del repositorio y patrones de unidad de trabajo
en una aplicación de MVC de ASP.NET, y controlador de pruebas lógica de (el ejemplo implementa el modelo de
repositorio).
Organización de la aplicación de prueba
La aplicación de prueba es una aplicación de consola en el tests/RazorPagesProject.Tests carpeta.
Recursos adicionales
Pruebas unitarias
Pruebas unitarias de páginas de Razor
Middleware
Controladores de pruebas
Pruebas de unidad de páginas de Razor en ASP.NET
Core
22/06/2018 • 18 minutes to read • Edit Online
Se pueden ejecutar las pruebas con las características integradas de prueba de un IDE, como Visual Studio. Si usa
código de Visual Studio o la línea de comandos, ejecute el siguiente comando en un símbolo del sistema en el
tests/RazorPagesTestSample.Tests carpeta:
dotnet test
Pruebas unitarias de la capa DAL requieren DbContextOptions al crear un nuevo AppDbContext para cada prueba.
Un enfoque para crear el DbContextOptions para cada prueba es usar un DbContextOptionsBuilder:
El problema con este enfoque es que cada prueba recibe la base de datos en el estado de la prueba anterior dejó.
Esto puede ser problemático al intentar escribir pruebas de unidad atómica que no interfieren entre sí. Para forzar
la AppDbContext para utilizar un nuevo contexto de base de datos para cada prueba, proporcione un
DbContextOptions instancia que se basa en un nuevo proveedor de servicios. La aplicación de prueba muestra
cómo hacer esto con su Utilities método de la clase TestingDbContextOptions
(tests/RazorPagesTestSample.Tests/Utilities/Utilities.cs):
return builder.Options;
}
Mediante el DbContextOptions en la unidad de la capa DAL pruebas permite que cada prueba para ejecutarse de
forma atómica con una instancia nueva de la base de datos:
if (message != null)
{
Messages.Remove(message);
await SaveChangesAsync();
}
}
Hay dos pruebas para este método. Una prueba comprueba que el método elimina un mensaje cuando el mensaje
está presente en la base de datos. Las demás pruebas de método que la base de datos no cambia si el mensaje
Id para su eliminación no existe. El DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound método se muestra
a continuación:
[Fact]
public async Task DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound()
{
using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
{
// Arrange
var seedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(seedMessages);
await db.SaveChangesAsync();
var recId = 1;
var expectedMessages =
seedMessages.Where(message => message.Id != recId).ToList();
// Act
await db.DeleteMessageAsync(recId);
// Assert
var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
Assert.Equal(
expectedMessages.OrderBy(x => x.Id),
actualMessages.OrderBy(x => x.Id),
new Utilities.MessageComparer());
}
}
En primer lugar, el método realiza el paso de organización, donde realiza la preparación para el paso de acción.
Los mensajes de propagación se obtienen y mantienen en seedMessages . Los mensajes de propagación se
guardan en la base de datos. El mensaje con un Id de 1 está establecido para su eliminación. Cuando el
DeleteMessageAsync método se ejecuta, los mensajes esperados deben tener todos los mensajes excepto el con
una Id de 1 . El expectedMessages variable representa este resultado esperado.
// Arrange
var seedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(seedMessages);
await db.SaveChangesAsync();
var recId = 1;
var expectedMessages =
seedMessages.Where(message => message.Id != recId).ToList();
Por último, el método obtiene la Messages desde el contexto y lo compara con el expectedMessages validar que los
dos son iguales:
// Assert
var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
[Fact]
public async Task DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound()
{
using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
{
// Arrange
var expectedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(expectedMessages);
await db.SaveChangesAsync();
var recId = 4;
// Act
await db.DeleteMessageAsync(recId);
// Assert
var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
}
}
Los métodos del modelo de página se prueban utilizando siete pruebas en la IndexPageTests clase
(tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs). Las pruebas usan el patrón de Act Assert
organizar familiarizado. Estas pruebas se centran en:
Determinar si los métodos siguen el comportamiento correcto cuando el ModelState no es válido.
Confirmar los métodos generan el valor correcto IActionResult .
Comprobando que las asignaciones de valor de propiedad se realizan correctamente.
Este grupo de pruebas a menudo simular los métodos de la capa DAL para generar datos esperado para el paso
de acción que se ejecuta un método de modelo de página. Por ejemplo, el GetMessagesAsync método de la
AppDbContext simulada para generar un resultado. Cuando un método de modelo de la página ejecuta este
método, el simulacro devuelve el resultado. Los datos no proceden de la base de datos. Esto crea condiciones de
prueba predecible y confiable para el uso de la capa DAL en las pruebas de modelo de página.
El OnGetAsync_PopulatesThePageModel_WithAListOfMessages prueba muestra cómo el GetMessagesAsync método es
simulado para el modelo de páginas:
Cuando el OnGetAsync método se ejecuta en el paso de acción, llama al modelo de página GetMessagesAsync
método.
Paso de acción de prueba unitaria (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs):
// Act
await pageModel.OnGetAsync();
El GetMessagesAsync método en la capa DAL no devuelve el resultado de llamar a este método. La versión del
método simulada devuelve el resultado.
En el Assert paso, los mensajes reales ( actualMessages ) se asignan a partir del Messages propiedad del modelo
de página. También se realiza una comprobación de tipo cuando se asignen los mensajes. Los mensajes esperados
y reales se comparan sus Text propiedades. La prueba valida que los dos List<Message> instancias contienen los
mismos mensajes.
// Assert
var actualMessages = Assert.IsAssignableFrom<List<Message>>(pageModel.Messages);
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
Otras pruebas en este grupo crean página de objetos del modelo que incluyen la DefaultHttpContext , el
ModelStateDictionary , ActionContext para establecer el PageContext , un ViewDataDictionary y un PageContext .
Estos son útiles para realizar las pruebas. Por ejemplo, la aplicación de mensaje establece una ModelState error
con AddModelError para comprobar que válido PageResult se devuelve cuando OnPostAddMessageAsync se ejecuta:
[Fact]
public async Task OnPostAddMessageAsync_ReturnsAPageResult_WhenModelStateIsInvalid()
{
// Arrange
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase("InMemoryDb");
var mockAppDbContext = new Mock<AppDbContext>(optionsBuilder.Options);
var expectedMessages = AppDbContext.GetSeedingMessages();
mockAppDbContext.Setup(db => db.GetMessagesAsync()).Returns(Task.FromResult(expectedMessages));
var httpContext = new DefaultHttpContext();
var modelState = new ModelStateDictionary();
var actionContext = new ActionContext(httpContext, new RouteData(), new PageActionDescriptor(),
modelState);
var modelMetadataProvider = new EmptyModelMetadataProvider();
var viewData = new ViewDataDictionary(modelMetadataProvider, modelState);
var tempData = new TempDataDictionary(httpContext, Mock.Of<ITempDataProvider>());
var pageContext = new PageContext(actionContext)
{
ViewData = viewData
};
var pageModel = new IndexModel(mockAppDbContext.Object)
{
PageContext = pageContext,
TempData = tempData,
Url = new UrlHelper(actionContext)
};
pageModel.ModelState.AddModelError("Message.Text", "The Text field is required.");
// Act
var result = await pageModel.OnPostAddMessageAsync();
// Assert
Assert.IsType<PageResult>(result);
}
Recursos adicionales
Pruebas unitarias de C# en .NET Core con xUnit y prueba de dotnet.
Controladores de pruebas
El código de prueba unitaria (Visual Studio)
Pruebas de integración
xUnit.net
Introducción a xUnit.net (.NET Core/ASP.NET Core)
Moq
Inicio rápido de Moq
Probar la lógica del controlador en ASP.NET Core
25/06/2018 • 27 minutes to read • Edit Online
Pruebas unitarias
Las pruebas unitarias conllevan probar una parte de una aplicación de forma aislada con respecto a su
infraestructura y dependencias. Cuando se realizan pruebas unitarias de la lógica de controlador, solo se
comprueba el contenido de una única acción, no el comportamiento de sus dependencias o del marco en sí.
Cuando realice pruebas unitarias de sus acciones de controlador, asegúrese de que solo se centran en el
comportamiento. Una prueba unitaria de controlador evita tener que recurrir a elementos como los filtros, el
enrutamiento o el enlace de modelos. Al centrarse en comprobar solo una cosa, las pruebas unitarias suelen ser
fáciles de escribir y rápidas de ejecutar. Un conjunto de pruebas unitarias bien escrito se puede ejecutar con
frecuencia sin demasiada sobrecarga. Pero las pruebas unitarias no detectan problemas de interacción entre
componentes, que es el propósito de las pruebas de integración.
Si está escribiendo filtros personalizados, rutas, etc., debería realizar pruebas unitarias en ellos, pero no como
parte de las comprobaciones de una acción de controlador determinada, sino de forma aislada.
TIP
Cree y ejecute pruebas unitarias con Visual Studio.
Para explicar las pruebas unitarias, revisaremos el siguiente controlador. Muestra una lista de sesiones de lluvia
de ideas y permite crear nuevas sesiones de lluvia de ideas con un método POST:
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using TestingControllersSample.Core.Interfaces;
using TestingControllersSample.Core.Model;
using TestingControllersSample.ViewModels;
namespace TestingControllersSample.Controllers
{
public class HomeController : Controller
{
private readonly IBrainstormSessionRepository _sessionRepository;
return View(model);
}
[HttpPost]
public async Task<IActionResult> Index(NewSessionModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
else
{
await _sessionRepository.AddAsync(new BrainstormSession()
{
DateCreated = DateTimeOffset.Now,
Name = model.SessionName
});
}
El controlador sigue el principio de dependencias explícitas, de modo que espera que la inserción de
dependencias le proporcione una instancia de IBrainstormSessionRepository . Esto es bastante sencillo de
comprobar si se usa un marco de objeto ficticio, como Moq. El método HTTP GET Index no tiene bucles ni
bifurcaciones y solamente llama a un método. Para probar este método Index , tenemos que confirmar que se
devuelve un ViewResult , con un ViewModel del método List del repositorio.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Moq;
using TestingControllersSample.Controllers;
using TestingControllersSample.Core.Interfaces;
using TestingControllersSample.Core.Model;
using TestingControllersSample.ViewModels;
using Xunit;
namespace TestingControllersSample.Tests.UnitTests
{
public class HomeControllerTests
{
[Fact]
public async Task Index_ReturnsAViewResult_WithAListOfBrainstormSessions()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.ListAsync()).Returns(Task.FromResult(GetTestSessions()));
var controller = new HomeController(mockRepo.Object);
// Act
var result = await controller.Index();
// Assert
var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsAssignableFrom<IEnumerable<StormSessionViewModel>>(
viewResult.ViewData.Model);
Assert.Equal(2, model.Count());
}
El método HomeController HTTP POST Index (mostrado arriba) debe comprobar lo siguiente:
El método de acción devuelve un ViewResult de solicitud incorrecta con los datos adecuados cuando
ModelState.IsValid es false .
[Fact]
public async Task IndexPost_ReturnsBadRequestResult_WhenModelStateIsInvalid()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.ListAsync()).Returns(Task.FromResult(GetTestSessions()));
var controller = new HomeController(mockRepo.Object);
controller.ModelState.AddModelError("SessionName", "Required");
var newSession = new HomeController.NewSessionModel();
// Act
var result = await controller.Index(newSession);
// Assert
var badRequestResult = Assert.IsType<BadRequestObjectResult>(result);
Assert.IsType<SerializableError>(badRequestResult.Value);
}
[Fact]
public async Task IndexPost_ReturnsARedirectAndAddsSession_WhenModelStateIsValid()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.AddAsync(It.IsAny<BrainstormSession>()))
.Returns(Task.CompletedTask)
.Verifiable();
var controller = new HomeController(mockRepo.Object);
var newSession = new HomeController.NewSessionModel()
{
SessionName = "Test Name"
};
// Act
var result = await controller.Index(newSession);
// Assert
var redirectToActionResult = Assert.IsType<RedirectToActionResult>(result);
Assert.Null(redirectToActionResult.ControllerName);
Assert.Equal("Index", redirectToActionResult.ActionName);
mockRepo.Verify();
}
La primera prueba confirma cuándo ModelState no es válido; se devuelve el mismo ViewResult que para una
solicitud GET . Cabe decir que la prueba no intenta pasar un modelo no válido. Eso no funcionaría de todas
formas, ya que el enlace de modelos no se está ejecutando (aunque una prueba de integración sí usaría el
enlace de modelos). En este caso concreto no estamos comprobando el enlace de modelos. Con estas pruebas
unitarias solamente estamos comprobando lo que el código del método de acción hace.
La segunda prueba comprueba si, cuando ModelState es válido, se agrega un nuevo BrainstormSession (a
través del repositorio) y el método devuelve un RedirectToActionResult con las propiedades que se esperan.
Las llamadas ficticias que no se efectúan se suelen omitir, aunque llamar a Verifiable al final de la llamada nos
permite confirmar esto en la prueba. Esto se logra con una llamada a mockRepo.Verify , que producirá un error
en la prueba si no se ha llamado al método esperado.
NOTE
La biblioteca Moq usada en este ejemplo nos permite mezclar fácilmente objetos ficticios comprobables (o "estrictos") con
objetos ficticios no comprobables (también denominados "flexibles" o stub). Obtenga más información sobre cómo
personalizar el comportamiento de objetos ficticios con Moq.
Otro controlador de la aplicación muestra información relacionada con una sesión de lluvia de ideas
determinada. Este controlador incluye lógica para tratar los valores de identificador no válidos:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using TestingControllersSample.Core.Interfaces;
using TestingControllersSample.ViewModels;
namespace TestingControllersSample.Controllers
{
public class SessionController : Controller
{
private readonly IBrainstormSessionRepository _sessionRepository;
return View(viewModel);
}
}
}
La acción de controlador tiene tres casos que comprobar, uno por cada instrucción return :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Moq;
using TestingControllersSample.Controllers;
using TestingControllersSample.Core.Interfaces;
using TestingControllersSample.Core.Model;
using TestingControllersSample.ViewModels;
using Xunit;
namespace TestingControllersSample.Tests.UnitTests
{
public class SessionControllerTests
{
[Fact]
public async Task IndexReturnsARedirectToIndexHomeWhenIdIsNull()
{
// Arrange
var controller = new SessionController(sessionRepository: null);
// Act
var result = await controller.Index(id: null);
// Assert
var redirectToActionResult = Assert.IsType<RedirectToActionResult>(result);
Assert.Equal("Home", redirectToActionResult.ControllerName);
Assert.Equal("Index", redirectToActionResult.ActionName);
}
[Fact]
public async Task IndexReturnsContentWithSessionNotFoundWhenSessionNotFound()
{
// Arrange
int testSessionId = 1;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.Returns(Task.FromResult((BrainstormSession)null));
var controller = new SessionController(mockRepo.Object);
// Act
var result = await controller.Index(testSessionId);
// Assert
var contentResult = Assert.IsType<ContentResult>(result);
Assert.Equal("Session not found.", contentResult.Content);
}
[Fact]
public async Task IndexReturnsViewResultWithStormSessionViewModel()
{
// Arrange
int testSessionId = 1;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.Returns(Task.FromResult(GetTestSessions().FirstOrDefault(s => s.Id == testSessionId)));
var controller = new SessionController(mockRepo.Object);
// Act
var result = await controller.Index(testSessionId);
// Assert
var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsType<StormSessionViewModel>(viewResult.ViewData.Model);
Assert.Equal("Test One", model.Name);
Assert.Equal(2, model.DateCreated.Day);
Assert.Equal(testSessionId, model.Id);
}
La aplicación expone la funcionalidad como una API web (una lista de ideas asociadas a una sesión de lluvia de
ideas y un método para agregar nuevas ideas a una sesión):
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using TestingControllersSample.ClientModels;
using TestingControllersSample.Core.Interfaces;
using TestingControllersSample.Core.Model;
namespace TestingControllersSample.Api
{
[Route("api/ideas")]
public class IdeasController : Controller
{
private readonly IBrainstormSessionRepository _sessionRepository;
[HttpGet("forsession/{sessionId}")]
public async Task<IActionResult> ForSession(int sessionId)
{
var session = await _sessionRepository.GetByIdAsync(sessionId);
if (session == null)
{
return NotFound(sessionId);
}
return Ok(result);
}
[HttpPost("create")]
public async Task<IActionResult> Create([FromBody]NewIdeaModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
await _sessionRepository.UpdateAsync(session);
return Ok(session);
}
}
}
El método ForSession devuelve una lista de tipos de IdeaDTO . Evite devolver entidades de dominio de empresa
directamente a través de llamadas API, ya que con frecuencia incluyen más datos de los que el cliente de API
requiere y asocian innecesariamente el modelo de dominio interno de su aplicación con la API que se expone
externamente. La asignación entre las entidades de dominio y los tipos que se van a devolver se puede realizar
manualmente (usando un método Select de LINQ, tal y como se muestra aquí) o por medio de una biblioteca
como AutoMapper.
Estas son las pruebas unitarias de los métodos API Create y ForSession :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Moq;
using TestingControllersSample.Api;
using TestingControllersSample.ClientModels;
using TestingControllersSample.Core.Interfaces;
using TestingControllersSample.Core.Model;
using Xunit;
namespace TestingControllersSample.Tests.UnitTests
{
public class ApiIdeasControllerTests
{
[Fact]
public async Task Create_ReturnsBadRequest_GivenInvalidModel()
{
// Arrange & Act
var mockRepo = new Mock<IBrainstormSessionRepository>();
var controller = new IdeasController(mockRepo.Object);
controller.ModelState.AddModelError("error","some error");
// Act
var result = await controller.Create(model: null);
// Assert
Assert.IsType<BadRequestObjectResult>(result);
}
[Fact]
public async Task Create_ReturnsHttpNotFound_ForInvalidSession()
{
// Arrange
int testSessionId = 123;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.Returns(Task.FromResult((BrainstormSession)null));
var controller = new IdeasController(mockRepo.Object);
// Act
var result = await controller.Create(new NewIdeaModel());
// Assert
Assert.IsType<NotFoundObjectResult>(result);
}
[Fact]
public async Task Create_ReturnsNewlyCreatedIdeaForSession()
{
// Arrange
int testSessionId = 123;
string testName = "test name";
string testDescription = "test description";
var testSession = GetTestSession();
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.Returns(Task.FromResult(testSession));
var controller = new IdeasController(mockRepo.Object);
// Act
var result = await controller.Create(newIdea);
// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
var returnSession = Assert.IsType<BrainstormSession>(okResult.Value);
mockRepo.Verify();
Assert.Equal(2, returnSession.Ideas.Count());
Assert.Equal(testName, returnSession.Ideas.LastOrDefault().Name);
Assert.Equal(testDescription, returnSession.Ideas.LastOrDefault().Description);
}
Como se ha indicado anteriormente, si quiere comprobar el comportamiento del método cuando ModelState
no es válido, agregue un error de modelo al controlador como parte de la prueba. No intente probar la
validación del modelo o el enlace de modelos en las pruebas unitarias: céntrese tan solo en el comportamiento
de su método de acción al confrontarlo con un valor de ModelState determinado.
La segunda prueba depende de que el repositorio devuelva null, por lo que el repositorio ficticio está
configurado para devolver un valor null. No es necesario crear una base de datos de prueba (en memoria o de
cualquier otro modo) ni crear una consulta que devuelva este resultado. Esto se puede realizar en una sola
instrucción, tal y como se muestra.
La última prueba confirma que se llama al método Update del repositorio. Tal y como hicimos anteriormente,
se llama al objeto ficticio con Verifiable y, después, se llama al método Verify del repositorio ficticio para
confirmar que el método Verifiable se ha ejecutado. Las pruebas unitarias no se encargan de garantizar que el
método Update guarda los datos; esto se puede realizar con una prueba de integración.
Pruebas de integración
Las pruebas de integración se realizan para garantizar que distintos módulos independientes dentro de la
aplicación funcionan correctamente juntos. Por lo general, todo lo que se puede comprobar con una prueba
unitaria también se puede comprobar con una prueba de integración, pero no a la inversa. Pero las pruebas de
integración suelen ser mucho más lentas que las unitarias. Por tanto, lo mejor es comprobar todo lo que sea
factible con las pruebas unitarias y recurrir a las pruebas de integración en los casos en los que existan varios
colaboradores.
Los objetos ficticios rara vez se usan en las pruebas de integración, aunque pueden seguir siendo de utilidad. En
las pruebas unitarias, los objetos ficticios constituyen un método eficaz de controlar el modo en que los
colaboradores fuera de la unidad que se está probando deben comportarse según los propósitos de la prueba.
En una prueba de integración se usan colaboradores reales para confirmar que todo el subsistema funciona
correctamente en conjunto.
Estado de la aplicación
Una consideración importante al realizar pruebas de integración es cómo establecer el estado de la aplicación.
Las pruebas se deben ejecutar de manera independiente entre sí, por lo que cada prueba debe comenzar con la
aplicación en un estado conocido. Que la aplicación no use una base de datos o tenga algún tipo de persistencia
no debe ser un problema. Pero la mayoría de las aplicaciones reales almacenan su estado en algún tipo de
almacén de datos, de modo que cualquier modificación que se realice a raíz de una prueba puede repercutir en
otra, a menos que se restablezca el almacén de datos. Si se usa el método integrado TestServer , será muy fácil
hospedar aplicaciones ASP.NET Core en nuestras pruebas de integración, pero esto no da acceso
necesariamente a los datos que se van a usar. Si se usa una base de datos real, un método consiste en conectar
la aplicación a una base de datos de prueba, a la que las pruebas pueden obtener acceso, y confirmar que está
restablecida en un estado conocido antes de que cada prueba se ejecute.
En esta aplicación de ejemplo, uso la base de datos InMemoryDatabase de Entity Framework Core, por lo que
no puedo simplemente conectarme a ella desde mi proyecto de prueba. En su lugar, expondré un método
InitializeDatabase desde la clase Startup de la aplicación, y llamaré a ese método cuando la aplicación se
inicie si está en el entorno Development . Mis pruebas de integración sacarán partido de esto automáticamente
siempre que tengan el entorno establecido en Development . No tiene que preocuparse de restablecer la base de
datos, ya que InMemoryDatabase se restablece cada vez que la aplicación se reinicia.
La clase Startup :
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using TestingControllersSample.Core.Interfaces;
using TestingControllersSample.Core.Model;
using TestingControllersSample.Infrastructure;
namespace TestingControllersSample
{
public class Startup
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>(
optionsBuilder => optionsBuilder.UseInMemoryDatabase("InMemoryDb"));
services.AddMvc();
services.AddScoped<IBrainstormSessionRepository,
EFStormSessionRepository>();
}
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
Verá que el método GetTestSession se usa con bastante asiduidad en las siguientes pruebas de integración.
Acceso a las vistas
En cada clase de prueba de integración se configura el método TestServer que ejecutará la aplicación de
ASP.NET Core. TestServer hospeda la aplicación web de forma predeterminada en la carpeta donde se está
ejecutando (en este caso, la carpeta del proyecto de prueba). Por tanto, si intenta probar las acciones del
controlador que devuelven ViewResult , es posible que aparezca este error:
The view 'Index' wasn't found. The following locations were searched:
(list of locations)
Para corregir este problema, debe configurar la raíz del contenido del servidor de forma que pueda localizar las
vistas del proyecto que se está comprobando. Esto se consigue con una llamada a UseContentRoot en la clase
TestFixture , como se aprecia aquí:
using System;
using System.IO;
using System.Net.Http;
using System.Reflection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.ViewComponents;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
namespace TestingControllersSample.Tests.IntegrationTests
{
/// <summary>
/// A test fixture which hosts the target project (project we wish to test) in an in-memory server.
/// </summary>
/// <typeparam name="TStartup">Target project's startup type</typeparam>
public class TestFixture<TStartup> : IDisposable
{
private readonly TestServer _server;
public TestFixture()
: this(Path.Combine("src"))
{
}
Client = _server.CreateClient();
Client.BaseAddress = new Uri("http://localhost");
}
services.AddSingleton(manager);
}
/// <summary>
/// Gets the full path to the target project that we wish to test
/// </summary>
/// <param name="projectRelativePath">
/// The parent directory of the target project.
/// e.g. src, samples, test, or test/Websites
/// </param>
/// <param name="startupAssembly">The target project's assembly.</param>
/// <returns>The full path to the target project.</returns>
private static string GetProjectPath(string projectRelativePath, Assembly startupAssembly)
{
// Get name of the target project which we want to test
var projectName = startupAssembly.GetName().Name;
throw new Exception($"Project root could not be located using the application root
{applicationBasePath}.");
}
}
}
La clase TestFixture se encarga de configurar y crear el método TestServer , que configura un HttpClient
para comunicarse con dicho método TestServer . En cada una de las pruebas de integración se usa la propiedad
Client para conectarse al servidor de prueba y realizar una solicitud.
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Xunit;
namespace TestingControllersSample.Tests.IntegrationTests
{
public class HomeControllerTests : IClassFixture<TestFixture<TestingControllersSample.Startup>>
{
private readonly HttpClient _client;
[Fact]
public async Task ReturnsInitialListOfBrainstormSessions()
{
// Arrange - get a session known to exist
var testSession = Startup.GetTestSession();
// Act
var response = await _client.GetAsync("/");
// Assert
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
Assert.Contains(testSession.Name, responseString);
}
[Fact]
public async Task PostAddsNewBrainstormSession()
{
// Arrange
string testSessionName = Guid.NewGuid().ToString();
var data = new Dictionary<string, string>();
data.Add("SessionName", testSessionName);
var content = new FormUrlEncodedContent(data);
// Act
var response = await _client.PostAsync("/", content);
// Assert
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.Equal("/", response.Headers.Location.ToString());
}
}
}
En la primera prueba de arriba, responseString contiene el HTML realmente presentado de la vista, que se
puede revisar para confirmar que contiene los resultados esperados.
La segunda prueba crea un formulario POST con un nombre de sesión único y lo envía a la aplicación para,
seguidamente, confirmar que se devuelve la redirección prevista.
Métodos de API
Si la aplicación expone API web, conviene confirmar que se ejecutan según lo previsto por medio de pruebas
automatizadas. El método integrado TestServer permite comprobar API web de forma muy sencilla. Si los
métodos de API usan el enlace de modelos, deberá comprobar siempre el factor ModelState.IsValid y, en este
sentido, las pruebas de integración son el lugar adecuado para confirmar que la validación del modelo funciona
correctamente.
El siguiente conjunto de pruebas tiene como destino el método Create en la clase IdeasController de arriba:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
using TestingControllersSample.ClientModels;
using TestingControllersSample.Core.Model;
using Xunit;
namespace TestingControllersSample.Tests.IntegrationTests
{
public class ApiIdeasControllerTests : IClassFixture<TestFixture<TestingControllersSample.Startup>>
{
internal class NewIdeaDto
{
public NewIdeaDto(string name, string description, int sessionId)
{
Name = name;
Description = description;
SessionId = sessionId;
}
[Fact]
public async Task CreatePostReturnsBadRequestForMissingNameValue()
{
// Arrange
var newIdea = new NewIdeaDto("", "Description", 1);
// Act
var response = await _client.PostAsJsonAsync("/api/ideas/create", newIdea);
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
public async Task CreatePostReturnsBadRequestForMissingDescriptionValue()
{
// Arrange
var newIdea = new NewIdeaDto("Name", "", 1);
// Act
var response = await _client.PostAsJsonAsync("/api/ideas/create", newIdea);
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
public async Task CreatePostReturnsBadRequestForSessionIdValueTooSmall()
{
// Arrange
// Arrange
var newIdea = new NewIdeaDto("Name", "Description", 0);
// Act
var response = await _client.PostAsJsonAsync("/api/ideas/create", newIdea);
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
public async Task CreatePostReturnsBadRequestForSessionIdValueTooLarge()
{
// Arrange
var newIdea = new NewIdeaDto("Name", "Description", 1000001);
// Act
var response = await _client.PostAsJsonAsync("/api/ideas/create", newIdea);
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
public async Task CreatePostReturnsNotFoundForInvalidSession()
{
// Arrange
var newIdea = new NewIdeaDto("Name", "Description", 123);
// Act
var response = await _client.PostAsJsonAsync("/api/ideas/create", newIdea);
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task CreatePostReturnsCreatedIdeaWithCorrectInputs()
{
// Arrange
var testIdeaName = Guid.NewGuid().ToString();
var newIdea = new NewIdeaDto(testIdeaName, "Description", 1);
// Act
var response = await _client.PostAsJsonAsync("/api/ideas/create", newIdea);
// Assert
response.EnsureSuccessStatusCode();
var returnedSession = await response.Content.ReadAsJsonAsync<BrainstormSession>();
Assert.Equal(2, returnedSession.Ideas.Count);
Assert.Contains(testIdeaName, returnedSession.Ideas.Select(i => i.Name).ToList());
}
[Fact]
public async Task ForSessionReturnsNotFoundForBadSessionId()
{
// Arrange & Act
var response = await _client.GetAsync("/api/ideas/forsession/500");
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task ForSessionReturnsIdeasForValidSessionId()
{
// Arrange
var testSession = Startup.GetTestSession();
// Act
// Act
var response = await _client.GetAsync("/api/ideas/forsession/1");
// Assert
response.EnsureSuccessStatusCode();
var ideaList = JsonConvert.DeserializeObject<List<IdeaDTO>>(
await response.Content.ReadAsStringAsync());
var firstIdea = ideaList.First();
Assert.Equal(testSession.Ideas.First().Name, firstIdea.Name);
}
}
}
A diferencia de las pruebas de integración de acciones que devuelven vistas HTML, los métodos de API web
que devuelven resultados se suelen poder deserializar como objetos fuertemente tipados, tal y como arroja la
última prueba mostrada arriba. En este caso, la prueba deserializa el resultado en una instancia de
BrainstormSession y confirma que la idea se agregó correctamente a la colección de ideas.
Se instalan las versiones de 32 y 64 bits de .NET Core SDK. Sólo las plantillas de las versiones de 64 bits
instaladas en ' C:\archivos de programa\dotnet\sdk\' se mostrará.
Esta advertencia aparece cuando (x86) 32 bits y las versiones de 64 bits (x 64) de la .NET Core SDK están
instalados. Ambas versiones pueden instalarse de motivos comunes son:
Descargar al instalador de .NET Core SDK con un equipo de 32 bits pero, a continuación, copiarla en y
originalmente había instalado en un equipo de 64 bits.
El SDK de núcleo de .NET de 32 bits se instaló por otra aplicación.
Se ha descargado e instalado una versión incorrecta.
Desinstale el SDK de núcleo de .NET de 32 bits para evitar esta advertencia. Desinstalar desde el Panel de
Control > programas y características > desinstalar o cambiar un programa. Si entiende por qué se produce
la advertencia y sus implicaciones, puede omitir la advertencia.
.NET Core SDK está instalado en varias ubicaciones
En el nuevo proyecto cuadro de diálogo para ASP.NET Core, verá la advertencia siguiente:
.NET Core SDK está instalado en varias ubicaciones. Sólo las plantillas de la SDK(s) instalado en ' C:\archivos
de programa\dotnet\sdk\' se mostrará.
Verá este mensaje cuando se tiene al menos una instalación de .NET Core SDK en un directorio fuera de
C:\archivos de programa\dotnet\sdk\. Normalmente esto sucede cuando se ha implementado el SDK de .NET
Core en una máquina con copiar y pegar en lugar del instalador MSI.
Desinstale el SDK de núcleo de .NET de 32 bits para evitar esta advertencia. Desinstalar desde el Panel de
Control > programas y características > desinstalar o cambiar un programa. Si entiende por qué se produce
la advertencia y sus implicaciones, puede omitir la advertencia.
No se detectaron ningún SDK de .NET Core
En el nuevo proyecto cuadro de diálogo para ASP.NET Core, verá la advertencia siguiente:
No se detectaron ningún SDK de .NET Core, asegúrese de que se incluyen en la variable de entorno 'PATH'.
Esta advertencia aparece cuando la variable de entorno PATH no apunta a los SDK de núcleo de .NET en el equipo.
Para resolver este problema:
Instalar o comprobar que está instalado el SDK de .NET Core.
Compruebe el PATH variable de entorno apunta a la ubicación que está instalado el SDK. El instalador
normalmente establece el PATH .
Uso de IHtmlHelper.Partial puede producir interbloqueos de aplicación
En ASP.NET Core 2.1 y versiones posteriores, al llamar a Html.Partial da como resultado una advertencia de
analizador debido a la posibilidad de interbloqueos. Es el mensaje de advertencia:
Las llamadas a @Html.Partial deben reemplazarse por @await Html.PartialAsync o la aplicación auxiliar de la
etiqueta parcial <partial name="_Partial" /> .
Trabajo con datos en ASP.NET Core
21/06/2018 • 2 minutes to read • Edit Online
Introducción a las páginas de Razor y Entity Framework Core con Visual Studio
Introducción a las páginas de Razor y EF
Operaciones de creación, lectura, actualización y eliminación
Ordenación, filtrado, paginación y agrupación
Migraciones
Creación de un modelo de datos complejo
Lectura de datos relacionados
Actualización de datos relacionados
Control de conflictos de simultaneidad
Introducción a ASP.NET Core MVC y Entity Framework Core con Visual Studio
Introducción
Operaciones de creación, lectura, actualización y eliminación
Ordenación, filtrado, paginación y agrupación
Migraciones
Creación de un modelo de datos complejo
Lectura de datos relacionados
Actualización de datos relacionados
Control de conflictos de simultaneidad
Herencia
Temas avanzados
ASP.NET Core con EF Core: nueva base de datos (sitio de la documentación de Entity Framework Core)
ASP.NET Core con EF Core: base de datos existente (sitio de la documentación de Entity Framework Core)
Introducción a ASP.NET Core y Entity Framework 6
Azure Storage
Agregar Azure Storage mediante el uso de Servicios conectados de Visual Studio
Introducción a Azure Blob Storage y Servicios conectados de Visual Studio
Introducción a Queue Storage y Servicios conectados de Visual Studio
Introducción a Azure Table Storage y Servicios conectados de Visual Studio
Páginas de Razor con Entity Framework Core en
ASP.NET Core: Tutorial 1 de 8
25/06/2018 • 32 minutes to read • Edit Online
Requisitos previos
Install one of the following:
CLI tooling: Windows, Linux, or macOS: .NET Core SDK 2.0 or later
IDE/editor tooling
Windows: Visual Studio for Windows
ASP.NET and web development workload
.NET Core cross-platform development workload
Linux: Visual Studio Code
macOS: Visual Studio for Mac
Familiaridad con las Páginas de Razor. Los programadores nuevos deben completar Introducción a las
páginas de Razor en ASP.NET Core antes de empezar esta serie.
Solución de problemas
Si experimenta un problema que no puede resolver, por lo general podrá encontrar la solución si compara el
código con la fase completada. Para obtener una lista de errores comunes y cómo resolverlos, vea la sección
de solución de problemas del último tutorial de la serie. Si ahí no encuentra lo que necesita, puede publicar
una pregunta en StackOverflow.com para ASP.NET Core o EF Core.
TIP
Esta serie de tutoriales se basa en lo que se realiza en los tutoriales anteriores. Considere la posibilidad de guardar
una copia del proyecto después de completar correctamente cada tutorial. Si experimenta problemas, puede empezar
desde el tutorial anterior en lugar de volver al principio. Como alternativa, puede descargar una fase completada y
empezar de nuevo con la fase completada.
Seleccione ASP.NET Core 2.0 en la lista desplegable y, luego, seleccione Aplicación web.
Presione F5 para ejecutar la aplicación en modo de depuración o Ctrl-F5 para que se ejecute sin adjuntar el
depurador.
Configurar el estilo del sitio
Con algunos cambios se configura el menú del sitio, el diseño y la página principal.
Abra Pages/_Layout.cshtml y realice los cambios siguientes:
Cambie todas las repeticiones de "ContosoUniversity" por "Contoso University". Hay tres
repeticiones.
Agregue entradas de menú para Students, Courses, Instructors y Departments, y elimine la
entrada de menú Contact.
Los cambios aparecen resaltados. (No se muestra todo el marcado).
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Contoso University</title>
<environment include="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
</environment>
<environment exclude="Development">
<link rel="stylesheet"
href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-
value="absolute" />
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
</environment>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-
collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a asp-page="/Index" class="navbar-brand">Contoso University</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a asp-page="/Index">Home</a></li>
<li><a asp-page="/About">About</a></li>
<li><a asp-page="/Students/Index">Students</a></li>
<li><a asp-page="/Courses/Index">Courses</a></li>
<li><a asp-page="/Instructors/Index">Instructors</a></li>
<li><a asp-page="/Departments/Index">Departments</a></li>
</ul>
</div>
</div>
</nav>
<div class="container body-content">
@RenderBody()
<hr />
<footer>
<p>© 2017 - Contoso University</p>
</footer>
</div>
En Pages/Index.cshtml, reemplace el contenido del archivo con el código siguiente para reemplazar el texto
sobre ASP.NET y MVC con texto sobre esta aplicación:
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<div class="jumbotron">
<h1>Contoso University</h1>
</div>
<div class="row">
<div class="col-md-4">
<h2>Welcome to Contoso University</h2>
<p>
Contoso University is a sample application that
demonstrates how to use Entity Framework Core in an
ASP.NET Core Razor Pages web app.
</p>
</div>
<div class="col-md-4">
<h2>Build it from scratch</h2>
<p>You can build the application by following the steps in a series of tutorials.</p>
<p><a class="btn btn-default"
href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro">
See the tutorial »</a></p>
</div>
<div class="col-md-4">
<h2>Download it</h2>
<p>You can download the completed project from GitHub.</p>
<p><a class="btn btn-default"
href="https://github.com/aspnet/Docs/tree/master/aspnetcore/data/ef-rp/intro/samples/cu-
final">
See project source code »</a></p>
</div>
</div>
Presione CTRL+F5 para ejecutar el proyecto. La página principal se muestra con las pestañas creadas en los
tutoriales siguientes:
Crear el modelo de datos
Cree las clases de entidad para la aplicación Contoso University. Comience con las tres entidades siguientes:
Hay una relación uno a varios entre las entidades Student y Enrollment . Hay una relación uno a varios
entre las entidades Course y Enrollment . Un estudiante se puede inscribir en cualquier número de cursos.
Un curso puede tener cualquier número de alumnos inscritos.
En las secciones siguientes, se crea una clase para cada una de estas entidades.
La entidad Student
Cree una carpeta Models. En la carpeta Models, cree un archivo de clase denominado Student.cs con el
código siguiente:
using System;
using System.Collections.Generic;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
La propiedad ID se convierte en la columna de clave principal de la tabla de base de datos (DB ) que
corresponde a esta clase. De forma predeterminada, EF Core interpreta como la clave principal una
propiedad que se denomine ID o classnameID . En classnameID , classname es el nombre de la clase (como
Student en el ejemplo anterior ).
namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}
La propiedad EnrollmentID es la clave principal. En esta entidad se usa el patrón classnameID en lugar de
ID como en la entidad Student . Normalmente, los desarrolladores eligen un patrón y lo usan en todo el
modelo de datos. En un tutorial posterior, se muestra el uso de ID sin un nombre de clase para facilitar la
implementación de la herencia en el modelo de datos.
La propiedad Grade es una enum . El signo de interrogación después de la declaración de tipo Grade indica
que la propiedad Grade acepta valores NULL. Una calificación que sea NULL es diferente de una
calificación que sea cero; NULL significa que no se conoce una calificación o que todavía no se ha asignado.
La propiedad StudentID es una clave externa y la propiedad de navegación correspondiente es Student .
Una entidad Enrollment está asociada con una entidad Student , por lo que la propiedad contiene una
única entidad Student . La entidad Student difiere de la propiedad de navegación Student.Enrollments , que
contiene varias entidades Enrollment .
La propiedad CourseID es una clave externa y la propiedad de navegación correspondiente es Course . Una
entidad Enrollment está asociada con una entidad Course .
EF Core interpreta una propiedad como una clave externa si se denomina
<navigation property name><primary key property name> . Por ejemplo, StudentID para la propiedad de
navegación Student , puesto que la clave principal de la entidad Student es ID . Las propiedades de clave
externa también se pueden denominar <primary key property name> . Por ejemplo CourseID , dado que la
clave principal de la entidad Course es CourseID .
La entidad Course
En la carpeta Models, cree Course.cs con el código siguiente:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
La propiedad Enrollments es una propiedad de navegación. Una entidad Course puede estar relacionada
con cualquier número de entidades Enrollment .
El atributo DatabaseGenerated permite que la aplicación especifique la clave principal en lugar de hacer que
la base de datos la genere.
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
Cuando se crea la base de datos, EF Core crea las tablas con los mismos nombres que los nombres de
propiedad DbSet . Los nombres de propiedad para las colecciones normalmente están en plural (Students
en lugar de Student). Los desarrolladores están en desacuerdo sobre si los nombres de tabla deben estar en
plural. Para estos tutoriales, se invalida el comportamiento predeterminado mediante la especificación de
nombres de tabla en singular en DbContext. Para especificar los nombres de tabla en singular, agregue el
código resaltado siguiente:
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
services.AddMvc();
}
El nombre de la cadena de conexión se pasa al contexto mediante una llamada a un método en un objeto
DbContextOptionsBuilder . Para el desarrollo local, el sistema de configuración de ASP.NET Core lee la
cadena de conexión desde el archivo appsettings.json.
Agregue instrucciones usingpara los espacios de nombres ContosoUniversity.Data y
Microsoft.EntityFrameworkCore . Compile el proyecto.
using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
Abra el archivo appsettings.json y agregue una cadena de conexión como se muestra en el código siguiente:
{
"ConnectionStrings": {
"DefaultConnection": "Server=
(localdb)\\mssqllocaldb;Database=ContosoUniversity1;ConnectRetryCount=0;Trusted_Connection=True;Multiple
ActiveResultSets=true"
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
}
}
En la cadena de conexión anterior se usa ConnectRetryCount=0 para evitar que SQLClient se bloquee.
SQL Server Express LocalDB
La cadena de conexión especifica una base de datos de SQL Server LocalDB. LocalDB es una versión ligera
del motor de base de datos de SQL Server Express y está dirigida al desarrollo de aplicaciones, no al uso en
producción. LocalDB se inicia a petición y se ejecuta en modo de usuario, sin necesidad de una
configuración compleja. De forma predeterminada, LocalDB crea archivos de base de datos .mdf en el
directorio C:/Users/<user> .
using ContosoUniversity.Models;
using System;
using System.Linq;
namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
context.Database.EnsureCreated();
El código comprueba si hay estudiantes en la base de datos. Si no hay ningún estudiante en la base de
datos, se inicializa con datos de prueba. Carga los datos de prueba en matrices en lugar de colecciones
List<T> para optimizar el rendimiento.
El método EnsureCreated crea automáticamente la base de datos para el contexto de base de datos. Si la
base de datos existe, EnsureCreated vuelve sin modificarla.
En Program.cs, modifique el método Main para que haga lo siguiente:
Obtener una instancia del contexto de base de datos desde el contenedor de inserción de dependencias.
Llamar al método de inicialización, pasándolo al contexto.
Eliminar el contexto cuando el método de inicialización finalice.
En el código siguiente se muestra el archivo Program.cs actualizado.
namespace ContosoUniversity
{
public class Program
{
public static void Main(string[] args)
{
var host = BuildWebHost(args);
host.Run();
}
La primera vez que se ejecuta la aplicación, se crea la base de datos y se inicializa con datos de prueba.
Cuando se actualice el modelo de datos:
Se elimina la base de datos.
Se actualiza el método de inicialización.
Se ejecuta la aplicación y se crea una base de datos inicializada.
En los tutoriales posteriores, la base de datos se actualiza cuando cambia el modelo de datos, sin tener que
eliminarla y volver a crearla.
Install-Package Microsoft.VisualStudio.Web.CodeGeneration.Design
Install-Package Microsoft.VisualStudio.Web.CodeGeneration.Utils
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Utils" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
</ItemGroup>
</Project>
dotnet restore
dotnet tool install --global dotnet-aspnet-codegenerator --version 2.1.0
dotnet aspnet-codegenerator razorpage -m Student -dc SchoolContext -udl -outDir Pages\Students --
referenceScriptLibraries
Si se produce un error:
Abra una ventana de comandos en el directorio del proyecto (el directorio que contiene los archivos
Program.cs, Startup.cs y .csproj).
Compile el proyecto. La compilación genera errores similares a los siguientes:
1>Pages\Students\Index.cshtml.cs(26,38,26,45): error CS1061: 'SchoolContext' does not contain a
definition for 'Student'
Cambie globalmente _context.Student por _context.Students (es decir, agregue una "s" a Student ). Se
encuentran y actualizan siete repeticiones. Esperamos solucionar este problema en la próxima versión.
En la tabla siguiente se incluyen los detalles de los parámetros de los generadores de código de ASP.NET
Core:
PARÁMETRO DESCRIPTION
Prueba de la aplicación
Ejecute la aplicación y haga clic en el vínculo Students. Según el ancho del explorador, el vínculo Students
aparece en la parte superior de la página. Si el vínculo Students no se ve, haga clic en el icono de
navegación en la esquina superior derecha.
Abra el Explorador de objetos de SQL Server (SSOX) desde el menú Vista en Visual Studio. En SSOX,
haga clic en (localdb)\MSSQLLocalDB > Databases > ContosoUniversity1.
Expanda el nodo Tablas.
Haga clic con el botón derecho en la tabla Student y haga clic en Ver datos para ver las columnas que se
crearon y las filas que se insertaron en la tabla.
Los archivos de base de datos .mdf y .ldf se encuentran en la carpeta C:\Usuarios\.
EnsureCreated se llama durante el inicio de la aplicación, lo que permite el flujo de trabajo siguiente:
Se elimina la base de datos.
Se cambia el esquema de base de datos (por ejemplo, se agrega un campo EmailAddress ).
Ejecute la aplicación.
EnsureCreated crea una base de datos con la columna EmailAddress .
Convenciones
La cantidad de código que se escribe para que EF Core cree una base de datos completa es mínima debido
al uso de convenciones o a las suposiciones que hace EF Core.
Los nombres de las propiedades DbSet se usan como nombres de tabla. Para las entidades a las que
no se hace referencia con una propiedad DbSet , los nombres de clase de entidad se usan como
nombres de tabla.
Los nombres de propiedad de entidad se usan para los nombres de columna.
Las propiedades de entidad que se denominan ID o classnameID se reconocen como propiedades de
clave principal.
Una propiedad se interpreta como propiedad de clave externa si se denomina (por ejemplo,
StudentID para la propiedad de navegación Student , dado que la clave principal de la entidad
Student es ID ). Las propiedades de clave externa también se pueden denominar (por ejemplo
EnrollmentID , dado que la clave principal de la entidad Enrollment es EnrollmentID ).
El comportamiento de las convenciones se puede reemplazar. Por ejemplo, los nombres de tabla se pueden
especificar explícitamente, como se muestra anteriormente en este tutorial. Los nombres de columna se
pueden establecer explícitamente. Las claves principales y las claves externas se pueden establecer
explícitamente.
Código asincrónico
La programación asincrónica es el modo predeterminado de ASP.NET Core y EF Core.
Un servidor web tiene un número limitado de subprocesos disponibles y, en situaciones de carga alta, es
posible que todos los subprocesos disponibles estén en uso. Cuando esto ocurre, el servidor no puede
procesar nuevas solicitudes hasta que los subprocesos se liberen. Con el código sincrónico, se pueden
acumular muchos subprocesos mientras no estén realizando ningún trabajo porque están a la espera de que
finalice la E/S. Con el código asincrónico, cuando un proceso está a la espera de que finalice la E/S, se libera
su subproceso para el que el servidor lo use para el procesamiento de otras solicitudes. Como resultado, el
código asincrónico permite que los recursos de servidor se usen de forma más eficaz, y el servidor está
habilitado para administrar más tráfico sin retrasos.
El código asincrónico introduce una pequeña cantidad de sobrecarga en tiempo de ejecución. En situaciones
de poco tráfico, la disminución del rendimiento es insignificante, mientras que en situaciones de tráfico
elevado, la posible mejora del rendimiento es importante.
En el código siguiente, la palabra clave async , el valor devuelto Task<T> , la palabra clave await y el
método ToListAsync hacen que el código se ejecute de forma asincrónica.
public async Task OnGetAsync()
{
Student = await _context.Students.ToListAsync();
}
Algunos aspectos que tener en cuenta al escribir código asincrónico en el que se usa EF Core son los
siguientes:
Solo se ejecutan de forma asincrónica las instrucciones que hacen que las consultas o los comandos
se envíen a la base de datos. Esto incluye ToListAsync , SingleOrDefaultAsync , FirstOrDefaultAsync y
SaveChangesAsync . No incluye las instrucciones que solo cambian una IQueryable , como
var students = context.Students.Where(s => s.LastName == "Davolio") .
S IG U IE N T E
ASP.NET Core MVC con EF Core: serie de tutoriales
21/06/2018 • 2 minutes to read • Edit Online
En este tutorial se explica el funcionamiento de ASP.NET Core MVC y Entity Framework Core con controladores y
vistas. Las páginas de Razor son una nueva alternativa en ASP.NET Core 2.0, un modelo de programación basado
en páginas que facilita la compilación de interfaces de usuario web y hace que sean más productivas.
Recomendamos el tutorial sobre las páginas de Razor antes que la versión MVC. El tutorial de las páginas de
Razor:
Es más fácil de seguir.
Proporciona más procedimientos recomendados de EF Core.
Usa consultas más eficaces.
Es más actual en relación con la API más reciente.
Abarca más características.
Es el método preferido para el desarrollo de nuevas aplicaciones.
Si elige este tutorial en lugar de la versión de páginas de Razor, le agradeceremos que nos explique el motivo en
este problema de GitHub.
1. Introducción
2. Operaciones de creación, lectura, actualización y eliminación
3. Ordenado, filtrado, paginación y agrupación
4. Migraciones
5. Creación de un modelo de datos complejo
6. Lectura de datos relacionados
7. Actualización de datos relacionados
8. Control de conflictos de simultaneidad
9. Herencia
10. Temas avanzados
Introducción a ASP.NET Core y Entity Framework 6
25/06/2018 • 7 minutes to read • Edit Online
Información general
Para usar Entity Framework 6, el proyecto se tiene que compilar con .NET Framework, dado que Entity
Framework 6 no es compatible con .NET Core. Si necesita usar características multiplataforma, debe actualizar a
Entity Framework Core.
La manera recomendada de usar Entity Framework 6 en una aplicación ASP.NET Core es incluir el contexto y las
clases de modelo de EF6 en un proyecto de biblioteca de clases que tenga como destino la plataforma completa.
Agregue una referencia a la biblioteca de clases desde el proyecto de ASP.NET Core. Vea el ejemplo Visual Studio
solution with EF6 and ASP.NET Core projects (Solución de Visual Studio con proyectos de EF6 y ASP.NET Core).
No se puede colocar un contexto de EF6 en un proyecto de ASP.NET Core porque los proyectos de .NET Core no
admiten toda la funcionalidad que requieren los comandos de EF6 como Enable-Migrations.
Independientemente del tipo de proyecto en el que localice el contexto de EF6, solo las herramientas de línea de
comandos de EF6 funcionan con un contexto de EF6. Por ejemplo, Scaffold-DbContext solo está disponible en
Entity Framework Core. Si necesita la utilización de técnicas de ingeniería inversa para una base de datos en un
modelo de EF6, vea Code First to an Existing Database (Code First para una base de datos existente).
<PropertyGroup>
<TargetFramework>net452</TargetFramework>
<PreserveCompilationContext>true</PreserveCompilationContext>
<AssemblyName>MVCCore</AssemblyName>
<OutputType>Exe</OutputType>
<PackageId>MVCCore</PackageId>
</PropertyGroup>
Al crear un proyecto, use la plantilla Aplicación web ASP.NET Core (.NET Framework).
Como el contexto de EF6 no tiene un constructor sin parámetros, el proyecto de EF6 tiene que proporcionar una
implementación de IDbContextFactory. Las herramientas de línea de comandos de EF6 buscarán y usarán esa
implementación para poder crear instancias del contexto. Este es un ejemplo.
En este ejemplo de código, la implementación de IDbContextFactory pasa una cadena de conexión codificada de
forma rígida. Se trata de la cadena de conexión que van a usar las herramientas de línea de comandos. Querrá
implementar una estrategia para asegurarse de que la biblioteca de clases usa la misma cadena de conexión que la
aplicación que realiza la llamada. Por ejemplo, podría obtener el valor de una variable de entorno en los dos
proyectos.
Después, puede obtener una instancia del contexto en los controladores mediante DI. El código es similar al que
escribiría para un contexto de EF Core:
Aplicación de ejemplo
Para obtener una aplicación de ejemplo funcional, vea la solución de Visual Studio de ejemplo que se incluye en
este artículo.
Este ejemplo se puede crear desde cero mediante los pasos siguientes en Visual Studio:
Cree una solución.
Agregar nuevo proyecto > Web > Aplicación web ASP.NET Core (.NET Framework)
Agregar nuevo proyecto > Escritorio clásico de Windows > Biblioteca de clases (.NET Framework)
En la Consola del Administrador de paquetes (PMC ) para ambos proyectos, ejecute el comando
Install-Package Entityframework .
En el proyecto de biblioteca de clases, cree clases de modelo de datos, una clase de contexto y una
implementación de IDbContextFactory .
En PMC para el proyecto de biblioteca de clases, ejecute los comandos Enable-Migrations y
Add-Migration Initial . Si ha configurado el proyecto de ASP.NET Core como proyecto de inicio, agregue
-StartupProjectName EF6 a estos comandos.
Resumen
En este artículo se proporcionan instrucciones básicas para el uso de Entity Framework 6 en una aplicación
ASP.NET Core.
Recursos adicionales
Entity Framework - Code-Based Configuration (Entity Framework: configuración basada en código)
Azure Storage en ASP.NET Core
21/06/2018 • 2 minutes to read • Edit Online
Uso de Gulp
Uso de Grunt
Administración de paquetes de cliente con Bower
Creación de sitios con capacidad de respuesta con Bootstrap
Aplicación de estilo a aplicaciones con LESS, Sass y Font Awesome
Agrupar y minimizar
TypeScript
Uso de Vínculo con exploradores
Uso de JavaScriptServices para aplicaciones de página única
Uso de plantillas de proyectos de aplicaciones de página única
Plantilla de proyecto Angular
Plantilla de proyecto React
Plantilla de proyecto React con Redux
Usar Gulp en ASP.NET Core
22/06/2018 • 18 minutes to read • Edit Online
gulp
Gulp es un toolkit de compilación streaming basadas en JavaScript para el código de cliente. Normalmente se
utiliza para secuenciar los archivos de cliente a través de una serie de procesos cuando se desencadena un evento
específico en un entorno de compilación. Por ejemplo, Gulp puede utilizarse para automatizar agrupar y minificar
o la limpieza de un entorno de desarrollo antes de una nueva compilación.
Se define un conjunto de tareas de Gulp en gulpfile.js. El siguiente código de JavaScript incluye módulos de Gulp
y especifica las rutas de acceso de archivo que se haga referencia dentro de las tareas disponibles próximamente:
var paths = {
webroot: "./wwwroot/"
};
El código anterior especifica qué módulos de nodo se necesitan. El require función importa cada módulo para
que las tareas dependientes pueden utilizar sus características. Cada uno de los módulos importados se asigna a
una variable. Los módulos pueden encontrarse por nombre o ruta de acceso. En este ejemplo, los módulos
denominan gulp , rimraf , gulp-concat , gulp-cssmin , y gulp-uglify se recuperan por su nombre. Además, se
crean una serie de rutas de acceso de modo que las ubicaciones de los archivos CSS y JavaScript se pueden
volver a usar y hace referencia en las tareas. En la tabla siguiente se proporciona descripciones de los módulos
incluidos en gulpfile.js.
NOMBRE DEL MÓDULO DESCRIPCIÓN
gulp cssmin Un módulo que minifica objeto archivos CSS. Para obtener
más información, consulte gulp cssmin.
uglify gulp Un módulo que minifica objeto .js archivos. Para obtener más
información, consulte uglify gulp.
Una vez que se importan los módulos necesarios, se pueden especificar las tareas. Hay seis tareas registrado,
representado por el código siguiente:
gulp.task("min:js", function () {
return gulp.src([paths.js, "!" + paths.minJs], { base: "." })
.pipe(concat(paths.concatJsDest))
.pipe(uglify())
.pipe(gulp.dest("."));
});
gulp.task("min:css", function () {
return gulp.src([paths.css, "!" + paths.minCss])
.pipe(concat(paths.concatCssDest))
.pipe(cssmin())
.pipe(gulp.dest("."));
});
En la tabla siguiente proporciona una explicación de las tareas especificadas en el código anterior:
limpiar: css Una tarea que usa el módulo de eliminación de nodo rimraf
para quitar la versión reducida del archivo site.css.
min:js Una tarea que minifica objeto y los concatena todos los
archivos .js dentro de la carpeta para js. El. se excluyen min.js
archivos.
min:CSS Una tarea que minifica objeto y los concatena todos los
archivos .css dentro de la carpeta de css. El. se excluyen
min.css archivos.
var paths = {
webroot: "./wwwroot/"
};
gulp.task("min:js", function () {
return gulp.src([paths.js, "!" + paths.minJs], { base: "." })
.pipe(concat(paths.concatJsDest))
.pipe(uglify())
.pipe(gulp.dest("."));
});
gulp.task("min:css", function () {
return gulp.src([paths.css, "!" + paths.minCss])
.pipe(concat(paths.concatCssDest))
.pipe(cssmin())
.pipe(gulp.dest("."));
});
{
"devDependencies": {
"gulp": "3.9.1",
"gulp-concat": "2.6.1",
"gulp-cssmin": "0.1.7",
"gulp-uglify": "2.0.1",
"rimraf": "2.6.1"
}
}
3. En el Explorador de soluciones, haga clic en gulpfile.jsy seleccione explorador del ejecutor de tareas.
Explorador del ejecutor de tareas muestra la lista de tareas de Gulp. (Tendrá que hacer clic en el
actualizar botón que aparece a la izquierda del nombre del proyecto.)
IMPORTANT
El explorador del ejecutor de tareas elemento de menú contextual aparece sólo si gulpfile.js está en el directorio
raíz del proyecto.
4. Debajo de la directiva tareas en explorador del ejecutor de tareas, haga clic en limpiay seleccione
ejecutar en el menú emergente.
Explorador del ejecutor de tareas creará una nueva ficha denominada limpia y ejecute la tarea clean tal
y como se define en gulpfile.js.
5. Haga clic en el limpia de tareas, a continuación, seleccione enlaces > antes de compilar.
El antes de compilar enlace configura la tarea clean se ejecute automáticamente antes de cada
compilación del proyecto.
Los enlaces configura con explorador del ejecutor de tareas se almacenan en forma de un comentario en la
parte superior de su gulpfile.js y son eficaces solo en Visual Studio. Es una alternativa que no requiere Visual
Studio configurar la ejecución automática de tareas gulp en su .csproj archivo. Por ejemplo, analizando estos datos
con su .csproj archivo:
Ahora la tarea clean se ejecuta cuando se ejecuta el proyecto en Visual Studio o desde un símbolo del sistema
mediante la dotnet ejecutar comando (ejecutar npm install primera).
gulp.task("first", function () {
console.log('first task! <-----');
});
gulp.task("series:first", function () {
console.log('first task! <-----');
});
Ahora que tiene tres tareas: series:first , series:second , y series . El series:second tarea incluye un
segundo parámetro que especifica una matriz de tareas que se ejecutarán y se complete antes de la
series:second se ejecutará la tarea. Como se especifica en el código anterior, solo la series:first tarea
debe completarse antes de la series:second se ejecutará la tarea.
2. Guardar gulpfile.js.
3. En el Explorador de soluciones, haga clic en gulpfile.js y seleccione explorador del ejecutor de tareas
si no está abierta.
4. En explorador del ejecutor de tareas, haga clic en serie y seleccione ejecutar.
IntelliSense
IntelliSense proporciona la finalización de código, descripciones de parámetros y otras características para
aumentar la productividad y reducir los errores. Tareas de gulp se escriben en JavaScript; por lo tanto, IntelliSense
puede proporcionar asistencia durante el desarrollo. Cuando se trabaja con JavaScript, IntelliSense enumera los
objetos, funciones, propiedades y parámetros que están disponibles según el contexto actual. Seleccione una
opción de codificación de la lista desplegable proporcionada IntelliSense para completar el código.
Para obtener más información acerca de IntelliSense, vea IntelliSense para JavaScript.
Para obtener información adicional sobre la referencia de API Gulp, consulte Gulp API de documentos.
Gulp recetas
La Comunidad Gulp proporciona Gulp recetas. Estas recetas constan de tareas de Gulp para dirigirse a escenarios
comunes.
Recursos adicionales
Documentación de gulp
Agrupar y minificar en ASP.NET Core
Usar Grunt en ASP.NET Core
Usar Grunt en ASP.NET Core
22/06/2018 • 17 minutes to read • Edit Online
Preparación de la aplicación
Para empezar, configure una nueva aplicación web vacía y agregar archivos de ejemplo de TypeScript. Archivos
de typeScript se compilan automáticamente en JavaScript con la configuración de Visual Studio de forma
predeterminada y serán nuestro materias primas para que procese utilizando Grunt.
1. En Visual Studio, cree un nuevo ASP.NET Web Application .
2. En el nuevo proyecto ASP.NET cuadro de diálogo, seleccione el núcleo de ASP.NET vacía plantilla y
haga clic en el botón Aceptar.
3. En el Explorador de soluciones, revise la estructura del proyecto. El \src carpeta incluye vacía wwwroot y
Dependencies nodos.
4. Agregar una nueva carpeta denominada TypeScript al directorio del proyecto.
5. Antes de agregar los archivos, asegúrese de que Visual Studio tiene la opción ' compilar al guardar ' para
comprobar los archivos TypeScript. Vaya a herramientas > opciones > Editor de texto > Typescript >
Proyecto:
6. Haga clic en el TypeScript directorio y seleccione Agregar > nuevo elemento en el menú contextual.
Seleccione el archivo JavaScript de elemento y un nombre al archivo Tastes.ts (tenga en cuenta el *.ts
extensión). Copie la línea de código TypeScript a continuación en el archivo (cuando se guarda, un nuevo
Tastes.js archivo aparecerá con el origen de JavaScript).
7. Agregar un segundo archivo en el TypeScript directorio y asígnele el nombre Food.ts . Copie el código
siguiente en el archivo.
class Food {
constructor(name: string, calories: number) {
this._name = name;
this._calories = calories;
}
Configuración de NPM
A continuación, configure NPM para descargar grunt y tareas grunt.
1. En el Explorador de soluciones, haga clic en el proyecto y seleccione Agregar > nuevo elemento en el
menú contextual. Seleccione el archivo de configuración de NPM item, deje el nombre
predeterminado, package.jsony haga clic en el agregar botón.
2. En el package.json de archivos, en la devDependencies objeto llaves, escriba "grunt". Seleccione grunt de
Intellisense, la lista y presione la tecla ENTRAR. Visual Studio entrecomillar el nombre del paquete grunt y
agregar un signo de dos puntos. A la derecha de los dos puntos, seleccione la versión estable más reciente
del paquete de la parte superior de la lista de Intellisense (presione Ctrl-Space si no aparece Intellisense).
NOTE
Usa NPM control de versiones semántico para organizar las dependencias. Control de versiones semántico, también
conocido como SemVer, identifica los paquetes con el esquema de numeración .. . IntelliSense simplifica el control de
versiones semántico presentando unas cuantas opciones comunes. El elemento superior en la lista de Intellisense
(0.4.5 en el ejemplo anterior) se considera la versión estable más reciente del paquete. El símbolo de intercalación
(^) coincide con la versión principal más reciente y la tilde () co in cide co n la versió n secu n daria más recien te. Consulte la
referencia del analizador de versión NPM semver como guía para la expresividad completa que proporciona SemVer.
3. Agregar más dependencias de carga grunt-hogar -* empaqueta para su limpia, jshint, concat, uglifyy
inspección tal como se muestra en el ejemplo siguiente. Las versiones no es necesario para que coincida
con el ejemplo.
"devDependencies": {
"grunt": "0.4.5",
"grunt-contrib-clean": "0.6.0",
"grunt-contrib-jshint": "0.11.0",
"grunt-contrib-concat": "0.5.1",
"grunt-contrib-uglify": "0.8.0",
"grunt-contrib-watch": "0.6.1"
}
NOTE
Si necesita, puede restaurar manualmente las dependencias en el Explorador de soluciones con el botón secundario en
Dependencies\NPM y seleccionando el restaurar paquetes opción de menú.
Configurar Grunt
Grunt se configura mediante un manifiesto llamado Gruntfile.js que define, carga y registra las tareas que se
pueden ejecutar manualmente o configuradas para ejecutarse automáticamente basándose en eventos en Visual
Studio.
1. Haga clic en el proyecto y seleccione Agregar > nuevo elemento. Seleccione el archivo de
configuración de Grunt opción, deje el nombre predeterminado, Gruntfile.jsy haga clic en el agregar
botón.
El código inicial incluye una definición de módulo y el grunt.initConfig() método. El initConfig() se usa
para establecer las opciones para cada paquete, y el resto del módulo se cargarán y registrar las tareas.
3. Debajo del método initConfig(), agregue una llamada a grunt.loadNpmTasks() . Esto hará que la tarea se
puede ejecutar desde Visual Studio.
grunt.loadNpmTasks("grunt-contrib-clean");
5. Haga clic en Gruntfile.js y seleccione explorador del ejecutor de tareas en el menú contextual. Se abrirá
la ventana del explorador del ejecutor de tareas.
7. Haga clic en la tarea clean y seleccione ejecutar en el menú contextual. Una ventana de comandos
muestra el progreso de la tarea.
NOTE
No hay ningún archivos o directorios para limpiar todavía. Si lo desea, puede crearlos manualmente en el Explorador
de soluciones y, a continuación, ejecutar la tarea clean como prueba.
8. En el método initConfig(), agregue una entrada para concat con el código siguiente.
El src matriz de propiedades incluyen archivos para combinar en el orden en que deben combinarse. El
dest propiedad asigna la ruta de acceso al archivo combinado que se genera.
concat: {
all: {
src: ['TypeScript/Tastes.js', 'TypeScript/Food.js'],
dest: 'temp/combined.js'
}
},
NOTE
El all propiedad en el código anterior es el nombre de un destino. Los destinos se usan en algunas tareas Grunt
para admitir varios entornos de compilación. Puede ver los destinos integrados mediante Intellisense o asignar la
suya propia.
jshint: {
files: ['temp/*.js'],
options: {
'-W069': false,
}
},
NOTE
La opción "-W069" es un error generado por jshint al corchete de cierre de JavaScript usa la sintaxis para asignar
una propiedad en lugar de la notación de puntos, es decir, Tastes["Sweet"] en lugar de Tastes.Sweet . La
opción desactiva la advertencia para permitir que el resto del proceso para continuar.
uglify: {
all: {
src: ['temp/combined.js'],
dest: 'wwwroot/lib/combined.min.js'
}
},
11. En grunt.loadNpmTasks() de llamada que carga grunt de limpieza del hogar, incluir la misma llamada para
jshint, concat y uglify con el código siguiente.
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
12. Guardar Gruntfile.js. El archivo debe tener un aspecto similar al ejemplo siguiente.
13. Tenga en cuenta que la lista de tareas del explorador de ejecutor de tareas incluye clean , concat , jshint
y uglify tareas. Ejecutar cada tarea en orden y observe los resultados en el Explorador de soluciones.
Cada tarea se debe ejecutar sin errores.
La tarea de concat crea un nuevo combined.js de archivo y lo coloca en el directorio temporal. La tarea de
jshint simplemente se ejecuta y no genera resultados. La tarea de uglify crea un nuevo combined.min.js de
archivo y lo coloca en wwwroot/lib. Al finalizar, la solución debe ser similar a la captura de pantalla
siguiente:
NOTE
Para obtener más información acerca de las opciones para cada paquete, visite https://www.npmjs.com/ y el nombre
del paquete en el cuadro de búsqueda en la página principal de búsqueda. Por ejemplo, puede buscar el paquete
grunt de limpieza del hogar para obtener un vínculo de la documentación que explica todos sus parámetros.
La nueva tarea aparece en el explorador del ejecutor de tareas en tareas de Alias. Puede haga y ejecutarla como
lo haría en otras tareas. El all tarea ejecutará clean , concat , jshint y uglify , en orden.
Observación de cambios
Un watch tarea vigila en archivos y directorios. La inspección desencadena tareas automáticamente si detecta los
cambios. Agregue el siguiente código para initConfig para inspeccionar los cambios realizados en *archivos .js en
el directorio TypeScript. Si se modifica un archivo de JavaScript, watch se ejecutará la all tarea.
watch: {
files: ["TypeScript/*.js"],
tasks: ["all"]
}
Agregue una llamada a loadNpmTasks() para mostrar la watch tarea en el explorador del ejecutor de tareas.
grunt.loadNpmTasks('grunt-contrib-watch');
Haga clic en la tarea de inspección de explorador del ejecutor de tareas y seleccione Ejecutar en el menú
contextual. Mostrará la ventana de comandos que muestra la tarea de inspección que se ejecuta un bucle
"esperando..." . Abra uno de los archivos TypeScript, agregue un espacio y, a continuación, guarde el archivo. Esto
desencadena la tarea de inspección y desencadenar las demás tareas que se ejecutan en orden. La captura de
pantalla siguiente muestra una ejemplo de ejecución.
Descargar y recargar el proyecto. Cuando se carga el proyecto de nuevo, iniciará la tarea de inspección ejecuta
automáticamente.
Resumen
Grunt es un ejecutor de tareas eficaz que puede utilizarse para automatizar la mayoría de las tareas de
compilación del cliente. Grunt aprovecha NPM para entregar sus paquetes, características y herramientas de
integración con Visual Studio. Explorador del ejecutor de tareas de Visual Studio detecta los cambios en archivos
de configuración y proporciona una interfaz adecuada para ejecutar tareas, ver las tareas en ejecución y enlazar
tareas a eventos de Visual Studio.
Recursos adicionales
Uso de Gulp
Administrar paquetes de cliente con Bower en
ASP.NET Core
22/06/2018 • 10 minutes to read • Edit Online
IMPORTANT
Mientras se mantiene Bower, sus mantenedores recomienda utilizar una solución distinta. Administrador de bibliotecas de
(LibMan abreviado) es el sistema de administración de contenido estático de cliente nuevo de Visual Studio (Visual Studio
15,8 o posterior). Para obtener más información, consulte Administrador de bibliotecas: Administrador de contenido de
cliente para las aplicaciones web. Bower se admite en Visual Studio a través de la versión 15,5.
Yarn con Webpack es una alternativa para la que instrucciones de migración están disponibles.
Bower llama a sí mismo "Administrador de paquetes para la web". Dentro del ecosistema de. NET, que se llena el
espacio vacío a la izquierda incapacidad de NuGet para entregar archivos de contenido estático. Para los proyectos
de ASP.NET Core, estos archivos estáticos son inherentes a las bibliotecas de cliente como jQuery y arranque. Para
las bibliotecas. NET, seguir usando NuGet Administrador de paquetes.
Proceso de compilación de proyectos nuevos creados con las plantillas de proyecto de ASP.NET Core configurar el
cliente. jQuery y arranque están instalados, y se admite Bower.
Paquetes de cliente se muestran en la bower.json archivo. Configura las plantillas de proyecto de ASP.NET Core
bower.json con jQuery, validación de jQuery y arranque.
En este tutorial, vamos a agregar compatibilidad para fuente Maravilla. Se pueden instalar paquetes de bower con
el administrar paquetes de Bower interfaz de usuario o manualmente en el bower.json archivo.
Instalación mediante la opción administrar paquetes de Bower interfaz de usuario
Crear una nueva aplicación Web de ASP.NET Core con el aplicación Web de ASP.NET Core (.NET Core)
plantilla. Seleccione aplicación Web y sin autenticación.
Haga clic en el proyecto en el Explorador de soluciones y seleccione administrar paquetes de Bower (o
bien en el menú principal, proyecto > administrar paquetes de Bower).
En el Bower: <nombre del proyecto> ventana, haga clic en la ficha "Examinar" y, a continuación, filtre la
lista de paquetes especificando font-awesome en el cuadro de búsqueda:
Confirme que la "guardar los cambios en bower.json" casilla de verificación está activada. Seleccione una
versión de la lista desplegable y haga clic en el instalar botón. El salida ventana muestra los detalles de
instalación.
Instalación manual en bower.json
Abra la bower.json de archivos y agregar "fuente maravillosa" a las dependencias. IntelliSense muestra los
paquetes disponibles. Cuando se selecciona un paquete, se muestran las versiones disponibles. Las siguientes
imágenes anteriores y no coincide con lo que se ve.
Usos de bower control de versiones semántico para organizar las dependencias. Control de versiones semántico,
también conocido como SemVer, identifica los paquetes con el esquema de numeración <principal >.< secundaria
>. <revisión >. IntelliSense simplifica el control de versiones semántico presentando unas cuantas opciones
comunes. El elemento superior en la lista de IntelliSense (4.6.3 en el ejemplo anterior) se considera la versión
estable más reciente del paquete. El símbolo de intercalación (^) coincide con la versión principal más reciente y la
tilde () coincide con la versión secundaria más reciente.
Guardar el bower.json archivo. Visual Studio busca la bower.json archivo para los cambios. Al guardar, el bower
install se ejecuta el comando. Vea la ventana de salida npm/Bower vista para el comando exacto que se ejecuta.
Abra la bowerrc de archivos en bower.json. El directory propiedad está establecida en wwwroot/lib que indica la
ubicación Bower instalará los activos de paquete.
{
"directory": "wwwroot/lib"
}
Puede usar el cuadro de búsqueda en el Explorador de soluciones para buscar y mostrar el paquete de fuente
Maravilla.
Abra la Views\Shared_Layout.cshtml de archivos y agregar el archivo CSS de fuente Maravilla en el entorno de
etiqueta auxiliar para Development . En el Explorador de soluciones, arrastre y coloque fuente awesome.css dentro
de la <environment names="Development"> elemento.
<environment names="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
<link href="~/lib/font-awesome/css/font-awesome.css" rel="stylesheet" />
</environment>
En una aplicación de producción agregaría fuente awesome.min.css a la aplicación auxiliar de etiquetas de entorno
para Staging,Production .
Reemplace el contenido de la Views\Home\About.cshtml archivo Razor con el marcado siguiente:
@{
ViewData["Title"] = "About";
}
<div class="list-group">
<a class="list-group-item" href="#"><i class="fa fa-home fa-fw" aria-hidden="true"></i> Home</a>
<a class="list-group-item" href="#"><i class="fa fa-book fa-fw" aria-hidden="true"></i> Library</a>
<a class="list-group-item" href="#"><i class="fa fa-pencil fa-fw" aria-hidden="true"></i>
Applications</a>
<a class="list-group-item" href="#"><i class="fa fa-cog fa-fw" aria-hidden="true"></i> Settings</a>
</div>
Ejecutar la aplicación y navegue hasta la vista acerca para comprobar el funciona de Maravilla de fuente del
paquete.
{
"name": "asp.net",
"private": true,
"dependencies": {
"jquery": "3.1.1",
"bootstrap": "3.3.7"
}
}
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
Paquetes de referencia
En esta sección, creará una página HTML para comprobar puede tener acceso a los paquetes implementados.
Agregar una nueva página HTML denominada Index.html a la wwwroot carpeta. Nota: Debe agregar el
archivo HTML para la wwwroot carpeta. De forma predeterminada, no se pueda servir contenido estático
fuera wwwroot. Vea archivos estáticos para obtener más información.
Reemplace el contenido de Index.html con el siguiente marcado:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Bower Example</title>
<link href="lib/bootstrap/dist/css/bootstrap.css" rel="stylesheet" />
</head>
<body>
<div class="jumbotron">
<h1>Using the jumbotron style</h1>
<p>
<a class="btn btn-primary btn-lg" role="button">Stateful button</a>
</p>
</div>
<script src="lib/jquery/dist/jquery.js"></script>
<script src="lib/bootstrap/dist/js/bootstrap.js"></script>
<script>
$(".btn").click(function () {
$(this).text('loading')
.delay(1000)
.queue(function () {
$(this).text('reset');
$(this).dequeue();
});
});
</script>
</body>
</html>
Primeros pasos
Hay varias maneras de empezar a trabajar con arranque. Si está iniciando una nueva aplicación web en Visual
Studio, puede elegir la plantilla de inicio predeterminado para ASP.NET Core, en el que se vienen preinstalada
Bootstrap mayúscula:
Adición de arranque a un núcleo de ASP.NET proyecto es simplemente cuestión de agregar a bower.json como
una dependencia:
{
"name": "asp.net",
"private": true,
"dependencies": {
"bootstrap": "3.3.6",
"jquery": "2.2.0",
"jquery-validation": "1.14.0",
"jquery-validation-unobtrusive": "3.2.6"
}
}
npm
NuGet
Install-Package bootstrap
NOTE
La manera recomendada para instalar las dependencias del lado cliente como programa previo de ASP.NET Core es a través
de Bower (mediante bower.json, tal y como se muestra arriba). Se muestran el uso de npm/NuGet para demostrar cómo
fácilmente arranque puede agregarse a otros tipos de aplicaciones web, incluidas las versiones anteriores de ASP.NET.
Si se están haciendo referencia a sus propias versiones locales de arranque, debe hacer referencia a ellos en todas
las páginas que va a usar. En producción, a que debería hacer referencia arrancar con una CDN. En la plantilla de
sitio ASP.NET de forma predeterminada, el _Layout.cshtml archivo tan similar a la siguiente:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - WebApplication1</title>
<environment names="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
</environment>
<environment names="Staging,Production">
<link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.6/css/bootstrap.min.css"
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-
value="absolute" />
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
</environment>
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-
collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a asp-area="" asp-controller="Home" asp-action="Index" class="navbar-
brand">WebApplication1</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<ul class="nav navbar-nav">
<li><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li>
<li><a asp-area="" asp-controller="Home" asp-action="About">About</a></li>
<li><a asp-area="" asp-controller="Home" asp-action="Contact">Contact</a></li>
</ul>
@await Html.PartialAsync("_LoginPartial")
</div>
</div>
</div>
<div class="container body-content">
@RenderBody()
<hr />
<footer>
<p>© 2016 - WebApplication1</p>
</footer>
</div>
<environment names="Development">
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
</environment>
<environment names="Staging,Production">
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js"
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery">
</script>
<script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.6/bootstrap.min.js"
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal">
</script>
<script src="~/js/site.min.js" asp-append-version="true"></script>
</environment>
NOTE
Si se va a usar cualquiera de los complementos de jQuery de Bootstrap, también debe hacer referencia a jQuery.
También incluye el nombre de la aplicación, que aparece en la esquina superior izquierda. El menú de navegación
principal se representa por la <ul> elemento dentro del segundo elemento div e incluye vínculos a casa,
aproximadamente y póngase en contacto con. A continuación el panel de navegación, el cuerpo principal de cada
página se representa en otra <div> , marcada con las clases "contenedor" y "contenido del cuerpo". En el archivo
de _Layout predeterminado simple se muestra aquí, el contenido de la página se representa mediante la vista
específica asociada a la página y, a continuación, una sencilla <footer> se agrega al final de la <div> elemento.
Puede ver cómo integrado acerca de la página aparece con esta plantilla:
La barra de navegación contraída, con el botón de "hamburguesa" en la parte superior derecha, aparece cuando la
ventana cae por debajo de un determinado ancho:
Haga clic en el icono muestra los elementos de menú en un espacio vertical que se desliza hacia abajo desde la
parte superior de la página:
Tipografía y vínculos
Bootstrap configura tipografía básico del sitio, colores y el formato en el archivo CSS del enlace. Este archivo CSS
incluye estilos predeterminados para las tablas, botones, elementos de formulario, imágenes etc. (más). Una
característica útil es el sistema de diseño de cuadrícula, trata más adelante.
Cuadrículas
Una de las características más populares de arranque es su sistema de diseño de cuadrícula. Aplicaciones web
modernas deben evitar usar la <table> etiqueta para el diseño, en su lugar, restringir el uso de este elemento de
datos tabulares reales. En su lugar, columnas y filas se pueden disponer mediante una serie de <div> elementos y
las clases CSS adecuadas. Hay varias ventajas con respecto a este enfoque, incluida la capacidad para ajustar el
diseño de cuadrículas para mostrar verticalmente en pantallas estrechas, como en teléfonos.
Sistema de diseño de cuadrícula del arranque se basa en doce columnas. Se ha elegido este número porque
puede dividirse uniformemente en 1, 2, 3 o 4 columnas y anchos de columna pueden variar a dentro de 1/12 del
ancho vertical de la pantalla. Para empezar a usar el sistema de diseño de cuadrícula, debe comenzar con un
contenedor de <div> y, a continuación, agregue una fila <div> , tal y como se muestra aquí:
<div class="container">
<div class="row">
...
</div>
</div>
A continuación, agregar más <div> elementos para cada columna y especifique el número de columnas que
<div> debe ocupar (fuera de 12 ) como parte de una clase CSS a partir de "col - md-". Por ejemplo, si desea
simplemente tiene dos columnas del mismo tamaño, usaría una clase de "col-md-6" para cada uno de ellos. En
este caso "md" es la abreviatura de "medio" y hace referencia a tamaños de pantalla del equipo de escritorio de
tamaño estándar. Hay cuatro opciones diferentes, que puede elegir entre y cada una se usará para ancho superior
a menos que se reemplaza (por lo que si desea que el diseño debe corregirse, independientemente del ancho de
pantalla, basta con especificar clases extra).
Al especificar dos columnas con "col-md-6" el diseño resultante será dos columnas en las soluciones de escritorio,
pero estas dos columnas se apilan verticalmente cuando se representa en dispositivos más pequeños (o una
ventana del explorador más restringida en un equipo de escritorio), lo que permite a los usuarios ver fácilmente
contenido sin necesidad de desplazarse horizontalmente.
Bootstrap siempre predeterminada será un diseño de columna única, por lo que basta con especificar columnas
cuando desee que más de una columna. La única vez que desearía especificar explícitamente que un <div> sería
ocupan 12 todas las columnas invalidar el comportamiento de un mayor nivel de dispositivo. Al especificar varias
clases de nivel de dispositivo, debe restablecer la presentación de las columnas en ciertos puntos. Agregar un
elemento div clearfix que solo es visible dentro de una ventanilla de cierta puede lograrlo, como se muestra aquí:
En el ejemplo anterior, uno y dos comparten una fila en el diseño "md", mientras que dos y tres comparten una
fila en el diseño de "xs". Sin el clearfix <div> , dos y tres no se muestran correctamente en la vista de "xs" (tenga
en cuenta que se muestra solo uno, cuatro y cinco):
En este ejemplo, una única fila <div> se utilizó, y sigue arranque principalmente ha sido lo correcto en relación
con el diseño y apilado de las columnas. Por lo general, debe especificar una fila <div> para cada fila horizontal
requiere el diseño y, por supuesto puede anidar arranque cuadrículas dentro de uno a otro. Al hacerlo, cada
cuadrícula anidada ocupan 100% del ancho del elemento en el que se coloca, que, a continuación, se subdividen
mediante las clases de columna.
Jumbotron
Si ha usado las plantillas ASP.NET MVC predeterminada en Visual Studio 2012 o 2013, probablemente ha visto el
Jumbotron en acción. Hace referencia a una sección grande de ancho completo de una página que puede usarse
para mostrar una imagen de fondo de gran tamaño, una llamada a la acción, una rotación o elementos similares.
Para agregar una jumbotron a una página, basta con agregar un <div> y asígnele una clase de "jumbotron", a
continuación, coloque un contenedor <div> dentro y agregar su contenido. Podemos ajustar fácilmente el
estándar acerca de la página que se usará un jumbotron para los encabezados principales muestra:
Botones
En la ilustración siguiente se muestran las clases del botón predeterminado y sus colores.
Distintivos
Distintivos hacen referencia a las llamadas pequeño, normalmente numéricos junto a un elemento de navegación.
Puede indicar un número de mensajes o notificaciones en espera, o a la presencia de actualizaciones. Especificar
tales notificaciones es tan sencillo como agregar una <span> que contiene el texto, con una clase de "tarjeta":
Alertas
Puede que necesite mostrar algún tipo de notificación, la alerta o el mensaje de error a los usuarios de la
aplicación. Es donde las clases estándar de alertas son útiles. Hay cuatro niveles de gravedad diferentes con
combinaciones de colores asociados:
Menús y barras de exploración
Nuestro diseño ya incluye una barra de navegación estándar, pero el tema de arranque es compatible con
opciones de estilo adicionales. Podemos realizar fácilmente decidimos para mostrar la barra de navegación
verticalmente en lugar de horizontalmente si lo que se prefiere, así como agregar navegación secundaria
elementos en los menús desplegables. Los menús de navegación sencillo, como bandas de ficha, se generan en la
parte superior de <ul> elementos. Estos pueden crearse muy basta con que se proporcionen con las clases CSS
"nav" y "nav pestañas":
Barras de exploración se generan de forma similar, pero son un poco más complejas. Se inician con un <nav> o
<div> con una clase de "barra de navegación," dentro del cual un elemento div contenedor contiene el resto de
los elementos. Nuestra página incluye una barra de navegación en el encabezado ya; se muestra a continuación
simplemente se expande en el objeto, agrega compatibilidad para un menú desplegable:
Elementos adicionales
También puede utilizarse el tema predeterminado para presentar tablas HTML en un estilo bien formateado,
incluida la compatibilidad con vistas con bandas. Hay etiquetas con estilos que son similares a las de los botones.
Puede crear menús de lista desplegable personalizados que admiten opciones de estilo adicionales más allá de
HTML estándar <select> elemento, junto con barras de exploración como el nuestro sitio de inicio
predeterminada ya está usando. Si necesita una barra de progreso, hay varios estilos para elegir, así como
enumerar los grupos y paneles que incluyen un título y el contenido. Explorar opciones adicionales en el tema de
arranque estándar aquí:
http://getbootstrap.com/examples/theme/
Más temas
Puede ampliar el tema de arranque estándar reemplazando algunos o todos sus CSS, ajustar los colores y estilos
para satisfacer las necesidades de su propia aplicación. Si desea iniciar desde un tema listos para su uso, hay
varias galerías del tema disponibles en línea que están especializados en temas de arranque, como
WrapBootstrap.com (que tiene una gran variedad de temas comerciales) y Bootswatch.com (que ofrece temas
libres). Algunas de las plantillas de pagadas disponibles proporcionan una gran cantidad de funcionalidad sobre el
tema de arranque básica, como compatibilidad enriquecida para los menús administrativos y los paneles con
medidores y gráficos enriquecidos. Un ejemplo de una plantilla de pago popular es Inspinia, actualmente para la
venta de $18, que incluye una plantilla de MVC5 de ASP.NET además de AngularJS y versiones HTML estáticas.
A continuación se muestra una captura de pantalla de ejemplo.
Si desea cambiar el tema del arranque, coloque el bootstrap.css archivo para el tema que desee en el
wwwroot/css carpeta y cambie las referencias en _Layout.cshtml para que lo señale. Cambie los vínculos para
todos los entornos:
<environment names="Development">
<link rel="stylesheet" href="~/css/bootstrap.css" />
<environment names="Staging,Production">
<link rel="stylesheet" href="~/css/bootstrap.min.css" />
Si desea crear su propio panel, puede iniciar desde el ejemplo libre está disponible aquí:
http://getbootstrap.com/examples/dashboard/ .
Componentes
Además de los elementos ya mencionados, arranque incluye compatibilidad para una gran variedad de
componentes de interfaz de usuario integrados.
Glyphicons
Bootstrap incluye conjuntos de iconos de Glyphicons (http://glyphicons.com), con más de 200 iconos libremente
disponibles para su uso dentro de la aplicación web habilitado para arranque. Aquí es sólo una pequeña muestra:
grupos de entrada
Grupos de entrada permiten la agrupación de botones con un elemento de entrada, o de texto adicional que
ofrece al usuario una experiencia más intuitiva:
Rutas de exploración
Rutas son un componente de interfaz de usuario común usado para mostrar su historial reciente o la profundidad
de la jerarquía de navegación de un sitio a un usuario. Agregarlos fácilmente mediante la aplicación de la clase
"ruta de navegación" a cualquier <ol> elemento de lista. Incluye compatibilidad integrada para la paginación
mediante el uso de la clase "paginación" en un <ul> elemento dentro de un <nav> . Agregar presentaciones
incrustados con capacidad de respuesta y vídeo con <iframe> , <embed> , <video> , o <object> elementos de
arranque se estilo automáticamente. Especifique una relación de aspecto determinada mediante el uso de clases
concretas como "Insertar-respondiendo-16by9".
Resumen
Bootstrap proporciona un marco web que puede usarse para diseñar y aplicar estilo a una gran variedad de sitios
Web y aplicaciones de manera rápida y productiva. Su tipografía básico y estilos proporcionan una apariencia y
funcionamiento agradable que pueden manipular fácilmente mediante la compatibilidad de tema personalizado,
que se puede crear manualmente o adquirió mercado. Admite una serie de componentes web que en el pasado,
tendrías que hacer costosos controles de otros fabricantes para lograr que la ejecución de los estándares web
modernos y abrir.
Menor, Sass y la fuente Maravilla en ASP.NET Core
22/06/2018 • 23 minutes to read • Edit Online
.header {
color: black;
font-weight: bold;
font-size: 18px;
font-family: Helvetica, Arial, sans-serif;
}
.small-header {
color: black;
font-weight: bold;
font-size: 14px;
font-family: Helvetica, Arial, sans-serif;
}
Con menor, esto se puede reescribir para eliminar la duplicación, todos con un mixin (denominada así porque
permite "¡mix" propiedades de una clase o conjunto de reglas en el otro):
.header {
color: black;
font-weight: bold;
font-size: 18px;
font-family: Helvetica, Arial, sans-serif;
}
.small-header {
.header;
font-size: 14px;
}
menos
El preprocesador de CSS inferior se ejecuta con Node.js. Para instalar menor, use el Administrador de paquetes de
nodo (npm) desde un símbolo del sistema (-g significa "global"):
Si está utilizando Visual Studio, puede empezar a trabajar con menor agregando uno o más archivos Less al
proyecto y, a continuación, configurando Gulp (o Grunt) para procesarlos en tiempo de compilación. Agregar un
estilos carpeta al proyecto y, a continuación, agregue un nuevo menos con el nombre de archivo main.less en esta
carpeta.
Una vez agregados, la estructura de carpetas debe tener un aspecto similar al siguiente:
Ahora puede agregar algunos estilos básicos para el archivo, que se compila en CSS e implementa en la carpeta
wwwroot Gulp.
Modificar main.less para incluir el contenido siguiente, que crea una paleta de colores simple desde un único color
base.
@base: #663333;
@background: spin(@base, 180);
@lighter: lighten(spin(@base, 5), 10%);
@lighter2: lighten(spin(@base, 10), 20%);
@darker: darken(spin(@base, -5), 10%);
@darker2: darken(spin(@base, -10), 20%);
body {
background-color:@background;
}
.baseColor {color:@base}
.bgLight {color:@lighter}
.bgLight2 {color:@lighter2}
.bgDark {color:@darker}
.bgDark2 {color:@darker2}
@base y el otro @-prefixed elementos son variables. Cada uno de ellos representa un color. Excepto @base , está
configurados con las funciones de color: aclarar, oscurecer y de número. Aclarar y oscurecer es prácticamente lo
que cabría esperar; número ajusta el matiz de un color en un número de grados (alrededor de la rueda de color). El
procesador de menos es lo suficientemente inteligente como para pasar por alto las variables que no se utilizan,
por lo que para demostrar cómo funcionan estas variables, es necesario utilizarlas en algún lugar. Las clases
.baseColor , etc. se muestran los valores calculados de cada una de las variables en el archivo CSS que se genera.
Primeros pasos
Crear un archivo de configuración de npm (package.json) en la carpeta del proyecto y editarlo para hacer
referencia a gulp y gulp-less :
{
"version": "1.0.0",
"name": "asp.net",
"private": true,
"devDependencies": {
"gulp": "3.9.1",
"gulp-less": "3.3.0"
}
}
Instalar las dependencias en un símbolo del sistema en la carpeta del proyecto, o en Visual Studio el Explorador
de soluciones (dependencias > npm > Restaurar paquetes).
npm install
En la carpeta del proyecto, cree una Gulp archivo de configuración (gulpfile.js) para definir el proceso
automatizado. Agregue una variable en la parte superior del archivo para representar menos y una tarea se ejecute
menor:
gulp.task("less", function () {
return gulp.src('Styles/main.less')
.pipe(less())
.pipe(gulp.dest('wwwroot/css'));
});
Abra la explorador del ejecutor de tareas (Vista > otras ventanas > Explorador del ejecutor de tareas).
Entre las tareas, verá una nueva tarea denominada less . Es posible que deba actualizar la ventana.
Ejecute la less tarea y obtener un resultado parecido al que se muestra aquí:
body {
background-color: #336666;
}
.baseColor {
color: #663333;
}
.bgLight {
color: #884a44;
}
.bgLight2 {
color: #aa6355;
}
.bgDark {
color: #442225;
}
.bgDark2 {
color: #221114;
}
Agregar una página HTML sencilla para la wwwroot carpeta y referencia main.css para ver la paleta de colores en
acción.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link href="css/main.css" rel="stylesheet" />
<title></title>
</head>
<body>
<div>
<div class="baseColor">BaseColor</div>
<div class="bgLight">Light</div>
<div class="bgLight2">Light2</div>
<div class="bgDark">Dark</div>
<div class="bgDark2">Dark2</div>
</div>
</body>
</html>
Puede ver que girar 180 grados en @base se usa para generar @background dio como resultado la rueda de color
opuestas color de @base :
Menor también proporciona compatibilidad para reglas anidadas, así como las consultas de media anidadas. Por
ejemplo, definir las jerarquías anidadas como menús pueden dar lugar a las reglas de CSS detalladas como estos:
nav {
height: 40px;
width: 100%;
}
nav li {
height: 38px;
width: 100px;
}
nav li a:link {
color: #000;
text-decoration: none;
}
nav li a:visited {
text-decoration: none;
color: #CC3333;
}
nav li a:hover {
text-decoration: underline;
font-weight: bold;
}
nav li a:active {
text-decoration: underline;
}
Lo ideal es que todas las reglas de estilo relacionados se colocarán juntos en el archivo CSS, pero en la práctica
que no hay nada aplicar esta regla excepto convención y quizás los comentarios del bloque.
Definir estas mismas reglas que reducen el uso es similar a esto:
nav {
height: 40px;
width: 100%;
li {
height: 38px;
width: 100px;
a {
color: #000;
&:link { text-decoration:none}
&:visited { color: #CC3333; text-decoration:none}
&:hover { text-decoration:underline; font-weight:bold}
&:active {text-decoration:underline}
}
}
}
Tenga en cuenta que en este caso, todos los elementos subordinados de nav están dentro de su ámbito. Ya no hay
ninguna repetición de elementos primarios ( nav , li , a ), y el recuento de líneas total ha disminuido también
(aunque algunos de que es el resultado de que los valores en las mismas líneas en el segundo ejemplo). Puede ser
muy útil, organización ver todas las reglas de un elemento de interfaz de usuario concreto dentro de un ámbito
explícitamente limitado, en este caso desactivar el resto del archivo de llaves.
El & sintaxis es una característica menos selector, con & que representa el elemento primario de selector actual.
Por lo tanto, dentro de la una {...} bloque, & representa un a (etiqueta) y, por tanto, &:link es equivalente a
a:link .
Consultas de medios, muy útiles para crear diseños de capacidad de respuesta, también pueden contribuir con un
alto grado a repeticiones y la complejidad de CSS. Menor permite consultas de medios se anidan dentro de las
clases, por lo que la definición de clase completo no tiene que repetirse dentro de otro nivel superior @media
elementos. Por ejemplo, mostramos CSS para un menú dinámico:
.navigation {
margin-top: 30%;
width: 100%;
}
@media screen and (min-width: 40em) {
.navigation {
margin: 0;
}
}
@media screen and (min-width: 62em) {
.navigation {
width: 960px;
margin: 0;
}
}
.navigation {
margin-top: 30%;
width: 100%;
@media screen and (min-width: 40em) {
margin: 0;
}
@media screen and (min-width: 62em) {
width: 960px;
margin: 0;
}
}
Otra característica de menor que ya hemos visto es su compatibilidad para operaciones matemáticas, lo que
permite a los atributos de estilo se construyan a partir de variables definidas previamente. Esto hará que la
actualización relacionados estilos mucho más fácil, ya que se puede modificar la variable de base y todos los
valores dependientes cambian automáticamente.
Archivos CSS, especialmente para sitios de gran tamaño (y especialmente si se usan las consultas de medios),
tienden a alcanzar un tamaño considerable con el tiempo, lo difícil trabajar con ellas. Menos archivos pueden
definirse por separado, a continuación, extrae juntos mediante @import directivas. También se puede usar menor
para importar CSS archivos individuales, también, si lo desea.
Mixins puede aceptar parámetros y menor admite una lógica condicional en forma de protecciones de mixin, que
proporcionan una manera declarativa para definir cuándo determinadas mixins surten efecto. Un uso común de
protecciones de mixin es ajustar los colores en función de la luz u oscuro el color de origen. Dado un mixin que
acepta un parámetro para el color, un guardia mixin puede utilizarse para modificar el mixin en función de ese
color:
.box (@color) when (lightness(@color) >= 50%) {
background-color: #000;
}
.box (@color) when (lightness(@color) < 50%) {
background-color: #FFF;
}
.box (@color) {
color: @color;
}
.feature {
.box (@base);
}
Dada nuestra actual @base valo #663333 , esta secuencia de comandos menos generará el código CSS siguiente:
.feature {
background-color: #FFF;
color: #663333;
}
Menor proporciona una serie de características adicionales, pero esto debe tener una idea de la potencia de este
lenguaje de preprocesamiento.
SASS
SASS es similar a la menor, lo que proporciona compatibilidad para muchas de las mismas características, pero
con una sintaxis ligeramente diferente. Se compila con Ruby, en lugar de JavaScript y, por lo que tiene requisitos
de instalación diferentes. El idioma de Sass original no usa llaves o punto y coma, sino que define ámbito mediante
espacios en blanco y sangría. En la versión 3 de Sass, se introdujo una nueva sintaxis, SCSS ("CSS Sassy"). SCSS
es similar a CSS en que se pasa por alto los espacios en blanco y niveles de sangría y en su lugar utiliza el punto y
coma y llaves.
Para instalar Sass, normalmente se instala por primera vez Ruby (preinstalado en Mac OS ) y, a continuación,
ejecute:
Sin embargo, si está ejecutando Visual Studio, puede empezar a trabajar con Sass prácticamente la misma manera
como lo haría con menor. Abra package.json y agregar el paquete "gulp sass" devDependencies :
"devDependencies": {
"gulp": "3.9.1",
"gulp-less": "3.3.0",
"gulp-sass": "3.1.0"
}
A continuación, modifique gulpfile.js para agregar una variable de sass y una tarea para compilar los archivos Sass
y colocar los resultados en la carpeta wwwroot:
var gulp = require("gulp"),
fs = require("fs"),
less = require("gulp-less"),
sass = require("gulp-sass");
gulp.task("sass", function () {
return gulp.src('Styles/main2.scss')
.pipe(sass())
.pipe(gulp.dest('wwwroot/css'));
});
Ahora puede agregar el archivo Sass main2.scss a la estilos carpeta en la raíz del proyecto:
$base: #CC0000;
body {
background-color: $base;
}
Guarde todos los archivos. Ahora, al actualizar explorador del ejecutor de tareas, verá un sass tarea. Ejecútelo
y, en la /wwwroot/css carpeta. Ahora hay un main2.css archivo con este contenido:
body {
background-color: #CC0000;
}
SASS admite el anidamiento prácticamente lo mismo era que menor no, proporcionar beneficios similares. Los
archivos se puede dividir función e incluido mediante la @import directiva:
@import 'anotherfile';
SASS admite mixins así, el uso de la @mixin palabra clave que se va a definirlos y @include para incluirlas, como
en este ejemplo de sass lang.com:
@mixin border-radius($radius) {
-webkit-border-radius: $radius;
-moz-border-radius: $radius;
-ms-border-radius: $radius;
border-radius: $radius;
}
Además de mixins, Sass también admite el concepto de herencia, lo que permite una clase Extender el otro. Es
conceptualmente similar a un mixin, pero da como resultado menos código CSS. Se realiza mediante el @extend
palabra clave. Para probar el mixins, agregue lo siguiente a su main2.scss archivo:
@mixin alert {
border: 1px solid black;
padding: 5px;
color: #333333;
}
.success {
@include alert;
border-color: green;
}
.error {
@include alert;
color: red;
border-color: red;
font-weight:bold;
}
Examine la salida en main2.css después de ejecutar el sass de tareas en explorador del ejecutor de tareas:
.success {
border: 1px solid black;
padding: 5px;
color: #333333;
border-color: green;
}
.error {
border: 1px solid black;
padding: 5px;
color: #333333;
color: red;
border-color: red;
font-weight: bold;
}
Tenga en cuenta que todas las propiedades comunes de la alerta mixin se repiten en cada clase. El mixin realizó un
buen trabajo para ayudar a eliminar la duplicación en tiempo de desarrollo, pero todavía está creando CSS con
una gran cantidad de duplicación en él, lo que produce mayores que los archivos necesarios de CSS - un posible
problema de rendimiento.
Ahora reemplace la alerta mixin con un .alert clase y cambiar @include a @extend (teniendo en cuenta ampliar
.alert , no alert ):
.alert {
border: 1px solid black;
padding: 5px;
color: #333333;
}
.success {
@extend .alert;
border-color: green;
}
.error {
@extend .alert;
color: red;
border-color: red;
font-weight:bold;
}
.success {
border-color: green;
}
.error {
color: red;
border-color: red;
font-weight: bold;
}
Ahora las propiedades se definen únicamente como tantas veces como sea necesario y mejor se genera CSS.
SASS también incluye funciones y operaciones de lógica condicional, similares a un valor inferior. De hecho, las
capacidades de los dos lenguajes son muy similares.
¿Menor o Sass?
Todavía no hay ningún consenso en cuanto a si en general es mejor usar menor o Sass (o incluso si se prefiere la
Sass original o la sintaxis SCSS más reciente en Sass). Probablemente la decisión más importante es utilizar una
de estas herramientas, en lugar de simplemente codificados manualmente los archivos CSS. Una vez que haya
realizado que ambos menor, la decisión y Sass son una buena elección.
Fuente Maravilla
Además de preprocesadores CSS, otro recurso excelente para aplicaciones web modernas de estilo es fantástica de
fuente. Awesome de fuente es un Kit de herramientas que proporciona más de 500 iconos vectoriales escalables
que pueden usarse libremente en sus aplicaciones web. Se diseñó originalmente para trabajar con arranque, pero
no tiene ninguna dependencia en ese marco de trabajo o en cualquier biblioteca de JavaScript.
La manera más fácil empezar a trabajar con fuentes Maravilla consiste en Agregar una referencia a ella, mediante
su ubicación de red (CDN ) de entrega de contenido público:
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
También puede agregarlo a su proyecto de Visual Studio, éste se agrega a las "dependencias" en bower.json:
{
"name": "ASP.NET",
"private": true,
"dependencies": {
"bootstrap": "3.0.0",
"jquery": "1.10.2",
"jquery-validation": "1.11.1",
"jquery-validation-unobtrusive": "3.2.2",
"hammer.js": "2.0.4",
"bootstrap-touch-carousel": "0.8.0",
"Font-Awesome": "4.3.0"
}
}
Una vez que tenga una referencia a la fuente Maravilla en una página, puede agregar iconos a la aplicación
aplicando fuente Maravilla clases, normalmente con el prefijo "fa-", a los elementos HTML alineado (como <span>
o <i> ). Por ejemplo, puede agregar iconos a listas simples y los menús con código similar al siguiente:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<link href="lib/font-awesome/css/font-awesome.css" rel="stylesheet" />
</head>
<body>
<ul class="fa-ul">
<li><i class="fa fa-li fa-home"></i> Home</li>
<li><i class="fa fa-li fa-cog"></i> Settings</li>
</ul>
</body>
</html>
Esto produce el siguiente en el explorador: tenga en cuenta el icono junto a cada elemento:
Resumen
Aplicaciones web modernas exigen cada vez más capacidad de respuesta, fluidos diseños que están limpios,
intuitiva y fácil de usar desde una variedad de dispositivos. Administrar la complejidad de las hojas de estilos CSS
necesaria para lograr estos objetivos mejor se realiza mediante un tipo de preprocesador menos o Sass. Además,
kits de herramientas como fuente Maravilla rápidamente proporcionan iconos conocidos para los menús de
navegación textual y experimentan de botones, mejorar la global del usuario de la aplicación.
Agrupación y minifiy activos estáticos en ASP.NET
Core
22/06/2018 • 18 minutes to read • Edit Online
Además de quitar los comentarios y espacios en blanco innecesarios, los siguientes nombres de parámetro y la
variable se cambió el nombre como sigue:
imageTagAndImageID t
imageContext a
imageElement r
Los exploradores son bastante detallados con respecto a los encabezados de solicitud HTTP. El número total de
bytes enviados métrica vio una reducción significativa cuando se agrupa. El tiempo de carga muestra una mejora
considerable, sin embargo, en este ejemplo se ejecutaba localmente. Mayores mejoras de rendimiento se percibe
cuando el uso de agrupación y minificación con activos transfiere a través de una red.
NOTE
BuildBundlerMinifier pertenece a un proyecto controlado por la Comunidad en GitHub para el que Microsoft no
proporciona compatibilidad con. Deben presentarse problemas aquí.
Visual Studio
CLI de .NET Core
Agregar el BuildBundlerMinifier paquete al proyecto.
Compile el proyecto. Aparecerá el siguiente mensaje en la ventana de salida:
1>------ Build started: Project: BuildBundlerMinifierApp, Configuration: Debug Any CPU ------
1>
1>Bundler: Begin processing bundleconfig.json
1> Minified wwwroot/css/site.min.css
1> Minified wwwroot/js/site.min.js
1>Bundler: Done processing bundleconfig.json
1>BuildBundlerMinifierApp -> C:\BuildBundlerMinifierApp\bin\Debug\netcoreapp2.0\BuildBundlerMinifierApp.dll
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
1>------ Clean started: Project: BuildBundlerMinifierApp, Configuration: Debug Any CPU ------
1>
1>Bundler: Cleaning output from bundleconfig.json
1>Bundler: Done cleaning output file from bundleconfig.json
========== Clean: 1 succeeded, 0 failed, 0 skipped ==========
NOTE
BundlerMinifier.Core pertenece a un proyecto controlado por la Comunidad en GitHub para el que Microsoft no
proporciona compatibilidad con. Deben presentarse problemas aquí.
Este paquete extiende la CLI de núcleo de .NET para incluir la dotnet agrupación herramienta. En la ventana de
consola de administrador de paquetes (PMC ) o en un shell de comandos, se puede ejecutar el comando siguiente:
dotnet bundle
IMPORTANT
Administrador de paquetes de NuGet agrega las dependencias en el archivo *.csproj como <PackageReference /> nodos.
El dotnet bundle comando está registrado con la CLI de .NET Core solo cuando un <DotNetCliToolReference /> nodo
se utiliza. Modifique el archivo *.csproj según corresponda.
footer {
margin-top: 10px;
}
A minificar custom.css y agrupar con site.css en un site.min.css , agregue la ruta de acceso relativa a
bundleconfig.json:
[
{
"outputFileName": "wwwroot/css/site.min.css",
"inputFiles": [
"wwwroot/css/site.css",
"wwwroot/css/custom.css"
]
},
{
"outputFileName": "wwwroot/js/site.min.js",
"inputFiles": [
"wwwroot/js/site.js"
],
"minify": {
"enabled": true,
"renameLocals": true
},
"sourceMap": false
}
]
NOTE
Como alternativa, podría utilizar el siguiente patrón de uso de comodines en:
"inputFiles": ["wwwroot/**/*(*.css|!(*.min.css)"]
Este patrón de uso de comodines coincide con todos los archivos CSS y excluye el patrón de archivo reducida.
Compile la aplicación. Abra site.min.css y observe el contenido de custom.css se anexa al final del archivo.
El siguiente environment etiqueta representa los archivos CSS agrupados y reducidos cuando se ejecuta en un
entorno distinto de Development . Por ejemplo, que se ejecutan Production o Staging desencadena el
procesamiento de estas hojas de estilos:
ASP.NET Core 2.x
ASP.NET Core 1.x
<environment exclude="Development">
<link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-
value="absolute" />
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
</environment>
NOTE
La extensión del paquete de instalación & Minificador pertenece a un proyecto controlado por la Comunidad en GitHub
para el que Microsoft no proporciona compatibilidad con. Deben presentarse problemas aquí.
El gulpfile.js y package.json archivos se agregan al proyecto. La compatibilidad con npm paquetes incluidos en el
package.json del archivo devDependencies sección están instalados.
Ejecute el siguiente comando en la ventana PMC para instalar la CLI Gulp como una dependencia global:
npm i -g gulp-cli
"use strict";
Convertir manualmente
Si Visual Studio o la extensión del paquete de instalación & Minificador no está disponible, convertir
manualmente.
Agregar un package.json archivo, con el siguiente devDependencies , a la raíz del proyecto:
"devDependencies": {
"del": "^3.0.0",
"gulp": "^3.9.1",
"gulp-concat": "^2.6.1",
"gulp-cssmin": "^0.2.0",
"gulp-htmlmin": "^3.0.0",
"gulp-uglify": "^3.0.0",
"merge-stream": "^1.0.1"
}
Instalar las dependencias ejecutando el siguiente comando en el mismo nivel que package.json:
npm i
npm i -g gulp-cli
"use strict";
var regex = {
css: /\.css$/,
html: /\.(html|htm)$/,
js: /\.js$/
js: /\.js$/
};
gulp.task("min:js", function () {
var tasks = getBundles(regex.js).map(function (bundle) {
return gulp.src(bundle.inputFiles, { base: "." })
.pipe(concat(bundle.outputFileName))
.pipe(uglify())
.pipe(gulp.dest("."));
});
return merge(tasks);
});
gulp.task("min:css", function () {
var tasks = getBundles(regex.css).map(function (bundle) {
return gulp.src(bundle.inputFiles, { base: "." })
.pipe(concat(bundle.outputFileName))
.pipe(cssmin())
.pipe(gulp.dest("."));
});
return merge(tasks);
});
gulp.task("min:html", function () {
var tasks = getBundles(regex.html).map(function (bundle) {
return gulp.src(bundle.inputFiles, { base: "." })
.pipe(concat(bundle.outputFileName))
.pipe(htmlmin({ collapseWhitespace: true, minifyCSS: true, minifyJS: true }))
.pipe(gulp.dest("."));
});
return merge(tasks);
});
gulp.task("clean", function () {
var files = bundleconfig.map(function (bundle) {
return bundle.outputFileName;
});
return del(files);
});
gulp.task("watch", function () {
getBundles(regex.js).forEach(function (bundle) {
gulp.watch(bundle.inputFiles, ["min:js"]);
});
getBundles(regex.css).forEach(function (bundle) {
gulp.watch(bundle.inputFiles, ["min:css"]);
});
getBundles(regex.html).forEach(function (bundle) {
gulp.watch(bundle.inputFiles, ["min:html"]);
});
});
function getBundles(regexPattern) {
return bundleconfig.filter(function (bundle) {
return regexPattern.test(bundle.outputFileName);
});
}
En este ejemplo, las tareas se definen en el MyPreCompileTarget destino ejecutar antes predefinido Build destino.
Aparecerá un resultado similar al siguiente en la ventana de salida de Visual Studio:
1>------ Build started: Project: BuildBundlerMinifierApp, Configuration: Debug Any CPU ------
1>BuildBundlerMinifierApp -> C:\BuildBundlerMinifierApp\bin\Debug\netcoreapp2.0\BuildBundlerMinifierApp.dll
1>[14:17:49] Using gulpfile C:\BuildBundlerMinifierApp\gulpfile.js
1>[14:17:49] Starting 'min:js'...
1>[14:17:49] Starting 'min:css'...
1>[14:17:49] Starting 'min:html'...
1>[14:17:49] Finished 'min:js' after 83 ms
1>[14:17:49] Finished 'min:css' after 88 ms
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
También se puede usar el explorador del ejecutor de tareas de Visual Studio para enlazar Gulp tareas a eventos
específicos de Visual Studio. Vea ejecutando tareas predeterminadas para obtener instrucciones sobre cómo
hacer que.
Recursos adicionales
Uso de Gulp
Uso de Grunt
Uso de varios entornos
Aplicaciones auxiliares de etiquetas
Vínculo de explorador en ASP.NET Core
22/06/2018 • 8 minutes to read • Edit Online
app.UseBrowserLink();
Normalmente es el código dentro de un if bloque que solo habilita el vínculo de explorador en el entorno de
desarrollo, como se muestra aquí:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
NOTE
Algunos complementos de Visual Studio, sobre todo 2015 de paquete de extensión de Web y 2017 de paquete de extensión
de Web, ofrecen una funcionalidad extendida de vínculo de explorador, pero algunas de las características adicionales no
funcionan con ASP. Proyectos de red principal.
Para abrir varios exploradores a la vez, elija examinar con... en la misma lista desplegable. Mantenga presionada
la tecla CTRL para seleccionar los exploradores que desee y, a continuación, haga clic en examinar:
Esta es una captura de pantalla que muestra Visual Studio con la vista de índice abierto y dos exploradores
abiertos:
Mantenga el mouse sobre el control de barra de herramientas de vínculo de explorador para ver los exploradores
que están conectados al proyecto:
Cambie la vista de índice y se actualizan todos los exploradores conectados al hacer clic en el botón de
actualización de vínculo de explorador:
Vínculo de explorador también funciona con exploradores que inicie desde fuera de Visual Studio y navegue a la
dirección URL de la aplicación.
El panel de vínculos de explorador
Abrir el panel de vínculo de explorador desde el menú para administrar la conexión con exploradores abiertos
desplegable de vínculo de explorador:
Si no hay ningún explorador está conectado, puede iniciar una sesión de depuración no seleccionando la ver en el
explorador vínculo:
En caso contrario, se muestran los exploradores conectados con la ruta de acceso a la página que se muestra cada
explorador:
Si lo desea, puede hacer clic en un nombre de lista del explorador para actualizar esa única del explorador.
Habilitar o deshabilitar el vínculo de explorador
Al volver a habilitar vínculo de explorador después de deshabilitarlo, debe actualizar los exploradores para volver a
conectarlos.
Habilitar o deshabilitar la sincronización automática de CSS
Cuando se habilita la sincronización automática de CSS, exploradores conectados se actualizan automáticamente
cuando se realiza cualquier cambio a los archivos CSS.
¿Cómo funciona?
Vínculo de explorador usa SignalR para crear un canal de comunicación entre Visual Studio y el explorador.
Cuando se habilita el vínculo de explorador, Visual Studio actúa como un servidor de SignalR que varios clientes
(exploradores) pueden conectarse a. Vínculo de explorador también registra un componente de middleware en la
canalización de solicitudes ASP.NET. Este componente inserta especial <script> referencias en cada solicitud de
página desde el servidor. Puede ver las referencias de script seleccionando ver código fuente en el explorador y
el desplazamiento hasta el final de la <body> etiquetar contenido:
No modifican los archivos de origen. El componente de middleware inserta dinámicamente las referencias de
script.
Dado que el código del lado del explorador es JavaScript todos, funciona en todos los exploradores compatibles
con SignalR sin necesidad de un complemento de explorador.
Usar JavaScriptServices para crear aplicaciones de
una página de ASP.NET Core
22/06/2018 • 22 minutes to read • Edit Online
¿Qué es JavaScriptServices?
JavaScriptServices es un conjunto de tecnologías de cliente para ASP.NET Core. Su objetivo consiste en colocar
ASP.NET Core como plataforma de servidor preferido de los desarrolladores para compilar SPAs.
JavaScriptServices consta de tres paquetes de NuGet distintos:
Microsoft.AspNetCore.NodeServices (NodeServices)
Microsoft.AspNetCore.SpaServices (SpaServices)
Microsoft.AspNetCore.SpaTemplates (SpaTemplates)
Estos paquetes son útiles si se:
Ejecutar JavaScript en el servidor
Usa un marco o biblioteca de SPA
Compilar recursos de lado cliente con Webpack
Gran parte de la atención en este artículo se coloca sobre cómo usar el paquete SpaServices.
¿Qué es SpaServices?
SpaServices se creó para colocar ASP.NET Core como la plataforma del lado servidor preferida de los
desarrolladores para compilar las SPA. SpaServices no es necesario para desarrollar SPA con ASP.NET Core y no
le obliga a usar un marco de cliente en particular.
SpaServices ofrece una infraestructura útil como:
Procesamiento previo de servidor
Middleware de desarrollo Webpack
Sustitución del módulo de acceso rápido
Aplicaciones auxiliares de enrutamientos
Colectivamente, estos componentes de infraestructura mejoran el flujo de trabajo de desarrollo y la experiencia en
tiempo de ejecución. Los componentes pueden adoptar individualmente.
Nota: Si va a implementar en un sitio web de Azure, no es necesario hacer nada aquí — Node.js está instalada y
disponible en los entornos de servidor.
.NET Core SDK 2.0 or later
Si está en Windows con Visual Studio de 2017, está instalado el SDK seleccionando la .NET Core el
desarrollo multiplataforma carga de trabajo.
Microsoft.AspNetCore.SpaServices paquete de NuGet
npm i -S aspnet-prerendering
Configuración
Las aplicaciones auxiliares de etiquetas se hacen reconocibles a través de registro del espacio de nombres en el
proyecto _ViewImports.cshtml archivo:
@using SpaServicesSampleApp
@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"
@addTagHelper "*, Microsoft.AspNetCore.SpaServices"
Estas aplicaciones auxiliares de etiquetas abstraer los detalles de comunicarse directamente con las API de bajo
nivel mediante el aprovechamiento de una sintaxis similar a HTML dentro de la vista Razor:
<app asp-prerender-module="ClientApp/dist/main-server">Loading...</app>
<app asp-prerender-module="ClientApp/dist/main-server"
asp-prerender-data='new {
UserName = "John Doe"
}'>Loading...</app>
Los datos recibidos UserName argumento se serializa utilizando el serializador JSON integrado y se almacena en la
params.data objeto. En el siguiente ejemplo Angular, los datos se utilizan para construir un saludo personalizado
dentro de un h1 elemento:
import { createServerRenderer, RenderResult } from 'aspnet-prerendering';
Nota: Los nombres de propiedad pasados en aplicaciones auxiliares de etiquetas se representan con PascalCase
notación. Esto contrasta con JavaScript, donde los mismos nombres de propiedad se representan con camelCase.
La configuración predeterminada de la serialización de JSON es responsable de esta diferencia.
Para ampliar el ejemplo de código anterior, datos pueden pasarse desde el servidor a la vista por hidratación el
globals propiedad proporcionada para el resolve función:
import { createServerRenderer, RenderResult } from 'aspnet-prerendering';
El postList matriz definida dentro de la globals objeto se adjunta en el explorador global window objeto. Esta
activación variables en ámbito global elimina la duplicación de esfuerzos, especialmente ya que pertenece a la
carga de los mismos datos una vez en el servidor y de nuevo en el cliente.
Requisitos previos
Instale el software siguiente:
ASPNET webpack npm paquete:
npm i -D aspnet-webpack
Configuración
Middleware de desarrollo Webpack está registrado en la canalización de solicitudes HTTP mediante el siguiente
código en el Startup.cs del archivo Configure método:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebpackDevMiddleware();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
El UseWebpackDevMiddleware método de extensión se debe llamar antes registrar archivos estáticos hospedaje a
través de la UseStaticFiles método de extensión. Por motivos de seguridad, registre el middleware solo cuando la
aplicación se ejecuta en modo de desarrollo.
El webpack.config.js del archivo output.publicPath propiedad indica el middleware para observar el dist carpeta
cambios:
npm i -D webpack-hot-middleware
Configuración
El componente HMR debe estar registrado en la canalización de solicitudes HTTP de MVC en el Configure
método:
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions {
HotModuleReplacement = true
});
Al igual que ocurría con Webpack Dev Middleware, UseWebpackDevMiddleware método de extensión se debe llamar
antes el UseStaticFiles método de extensión. Por motivos de seguridad, registre el middleware solo cuando la
aplicación se ejecuta en modo de desarrollo.
El webpack.config.js archivo debe definir una plugins de las matrices, incluso si se deja vacía:
npm i -S @angular/router
Configuración
Un método de extensión denominado MapSpaFallbackRoute se utiliza en el Configure método:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
Sugerencia: Las rutas se evalúan en el orden en el que está configurados. Por lo tanto, la default ruta en el
ejemplo de código anterior se utiliza en primer lugar para la coincidencia.
Para crear un nuevo proyecto con una de las plantillas SPA, incluya el nombre corto de la plantilla en el dotnet
nueva comando. El siguiente comando crea una aplicación Angular con ASP.NET MVC de núcleo configurado para
el lado del servidor:
dotnet run
Probar la aplicación
Las plantillas de SpaServices están preconfiguradas para ejecutar pruebas del lado cliente mediante Karma y
Jasmine. Jasmine es un marco de pruebas unitarias popular para JavaScript, mientras que Karma es un ejecutor de
esas pruebas. Karma está configurado para funcionar con Webpack Dev Middleware, de forma que el
desarrollador no tenga que parar y ejecutar la prueba cada vez que se realicen cambios. Tanto si se ejecuta el
código en el caso de prueba como si se trata del propio caso de prueba, la prueba se ejecuta automáticamente.
Usar la aplicación Angular como ejemplo, dos casos de prueba criterio Comidos ya se proporcionan para que la
CounterComponent en el counter.component.spec.ts archivo:
it('should start with count 0, then increments by 1 when clicked', async(() => {
const countElement = fixture.nativeElement.querySelector('strong');
expect(countElement.textContent).toEqual('0');
npm test
La secuencia de comandos inicia el ejecutor de pruebas Karma, que lee la configuración definida en el karma.conf.js
archivo. Entre otras opciones, el karma.conf.js identifica los archivos de prueba para ejecutarse a través de su
files matriz:
Publicar la aplicación
Combinación de los activos de cliente generados y los artefactos de ASP.NET Core publicados en un paquete listo
para implementar puede resultar complicada. Por suerte, SpaServices orquesta ese proceso de publicación
completa con un destino de MSBuild personalizado denominado RunWebpack :
Recursos adicionales
Documentos angulares
Uso de las plantillas de aplicación de una sola página
con ASP.NET Core
21/06/2018 • 2 minutes to read • Edit Online
NOTE
El SDK de .NET Core 2.0.x publicado incluye plantillas de proyecto anteriores para Angular, React y React con Redux. En esta
documentación no se tratan las plantillas de proyecto antiguas. En esta documentación se tratan las últimas plantillas para
Angular, React y React con Redux, que se pueden instalar manualmente en ASP.NET Core 2.0. Las plantillas se incluyen de
forma predeterminada con ASP.NET Core 2.1.
Requisitos previos
.NET Core SDK 2.0 or later
Node.js, versión 6 o posterior
Instalación
Las plantillas ya están instaladas con ASP.NET Core 2.1.
Si tiene ASP.NET Core 2.0, ejecute el comando siguiente para instalar las plantillas actualizadas de ASP.NET Core
para Angular, React y React con Redux:
NOTE
Esta documentación no trata sobre la plantilla de proyecto de Angular incluida en ASP.NET Core 2.0. Trata sobre la nueva
plantilla de Angular que puede actualizar manualmente. La plantilla se incluye de forma predeterminada en ASP.NET Core 2.1.
La plantilla de proyecto de Angular actualizada proporciona un práctico punto de partida para las aplicaciones
ASP.NET Core que usan Angular y la CLI de Angular para implementar una completa interfaz de usuario (UI) del
lado cliente.
La plantilla es equivalente a crear un proyecto de ASP.NET Core que funciona como back-end de API y un
proyecto de la CLI de Angular que funciona como interfaz de usuario. La plantilla ofrece la ventaja de hospedar
ambos tipos de proyecto en un único proyecto de aplicación. Por lo tanto, el proyecto de aplicación se puede
compilar y publicar como una sola unidad.
Ejecución de comandos ng
En un símbolo del sistema, cambie al subdirectorio ClientApp:
cd ClientApp
Si tiene instalada la herramienta ng globalmente, puede ejecutar cualquiera de sus comandos. Por ejemplo,
puede ejecutar ng lint , ng test , o cualquiera de los otros comandos de la CLI de Angular. Si bien, no es
necesario ejecutar ng serve , dado que la aplicación ASP.NET Core se ocupa de atender las partes del lado cliente
y servidor de la aplicación. Internamente, usa ng serve en el desarrollo.
Si no tiene instalada la herramienta ng , ejecute en su lugar npm run ng . Por ejemplo, puede ejecutar
npm run ng lint o npm run ng test .
cd ClientApp
npm install --save <package_name>
Publicación e implementación
En el desarrollo, la aplicación se ejecuta en modo optimizado para comodidad del desarrollador. Por ejemplo, las
agrupaciones de JavaScript incluyen asignaciones de origen (de modo que, durante la depuración, puede ver el
código original de TypeScript). La aplicación inspecciona los cambios en los archivos de TypeScript, HTML y CSS
en el disco y, automáticamente, realiza una nueva compilación y recarga cuando observa que esos archivos han
cambiado.
En producción, use una versión de la aplicación que esté optimizada para el rendimiento. Esto se configura para
que tenga lugar automáticamente. Al publicar, la configuración de compilación emite una compilación Ahead Of
Time (AoT) reducida del código del lado cliente. A diferencia de la compilación de desarrollo, la compilación de
producción no requiere la instalación de Node.js en el servidor (a no ser que haya habilitado la representación
previa del lado servidor).
Puede usar métodos de implementación y hospedaje de ASP.NET Core estándar.
cd ClientApp
npm start
IMPORTANT
Use npm start para iniciar el servidor de desarrollo de la CLI de Angular, no ng serve , de modo que la
configuración de package.json se respete. Para pasar parámetros adicionales al servidor de la CLI de Angular,
agréguelos a la línea de scripts correspondiente de su archivo package.json.
2. Modifique la aplicación ASP.NET Core para usar la instancia externa de la CLI de Angular en lugar de
iniciar una de las suyas. En la clase Startup, reemplace la invocación de spa.UseAngularCliServer por lo
siguiente:
spa.UseProxyToSpaDevelopmentServer("http://localhost:4200");
Cuando inicie la aplicación ASP.NET Core, no se iniciará un servidor de la CLI de Angular. En su lugar, se usa la
instancia que inició manualmente. Esto le permite iniciar y reiniciar con mayor rapidez. Ya no tiene que esperar a
que la CLI de Angular vuelva a compilar la aplicación cliente una y otra vez.
TIP
Habilitar la representación del lado servidor (SSR) conlleva una serie de complicaciones adicionales tanto durante el
desarrollo como durante la implementación. La las desventajas de SSR para determinar si SSR es una buena opción para sus
requisitos.
spa.UseSpaPrerendering(options =>
{
options.BootModulePath = $"{spa.Options.SourcePath}/dist-server/main.bundle.js";
options.BootModuleBuilder = env.IsDevelopment()
? new AngularCliBuilder(npmScript: "build:ssr")
: null;
options.ExcludeUrls = new[] { "/sockjs-node" };
});
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
En modo de desarrollo, este código intenta compilar la agrupación de SSR mediante la ejecución del script
build:ssr , que se define en ClientApp\package.json. Esta acción compila una aplicación de Angular denominada
ssr , que aún no se ha definido.
Al final de la matriz apps en ClientApp/.angular-cli.json, defina una aplicación adicional con el nombre ssr . Use
las siguientes opciones:
{
"name": "ssr",
"root": "src",
"outDir": "dist-server",
"assets": [
"assets"
],
"main": "main.server.ts",
"tsconfig": "tsconfig.server.json",
"prefix": "app",
"scripts": [],
"environmentSource": "environments/environment.ts",
"environments": {
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
},
"platform": "server"
}
Esta nueva configuración de aplicación habilitada para SSR requiere dos archivos más: tsconfig.server.json y
main.server.ts. El archivo tsconfig.server.json especifica las opciones de compilación de TypeScript. El archivo
main.server.ts sirve de punto de entrada de código durante SSR.
Agregue un nuevo archivo llamado tsconfig.server.json dentro de ClientApp/src (al lado del archivo
tsconfig.app.json existente), que contenga lo siguiente:
{
"extends": "../tsconfig.json",
"compilerOptions": {
"baseUrl": "./",
"module": "commonjs"
},
"angularCompilerOptions": {
"entryModule": "app/app.server.module#AppServerModule"
}
}
Este archivo configura el compilador AoT de Angular para buscar un módulo llamado app.server.module . Para
agregar el archivo, cree un nuevo archivo en ClientApp/src/app/app.server.module.ts (al lado del archivo
app.module.ts existente), que contenga lo siguiente:
@NgModule({
imports: [AppModule, ServerModule, ModuleMapLoaderModule],
bootstrap: [AppComponent]
})
export class AppServerModule { }
Este módulo hereda del objeto app.module del lado cliente y define qué módulos adicionales de Angular están
disponibles durante SSR.
Recuerde que la nueva entrada ssr en .angular-cli.json hacía referencia a un archivo de punto de entrada llamado
main.server.ts. Ese archivo no se ha agregado aún, y ahora es el momento de hacerlo. Cree un nuevo archivo en
ClientApp/src/main.server.ts ( junto al archivo main.ts existente), que contenga lo siguiente:
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import { renderModule, renderModuleFactory } from '@angular/platform-server';
import { APP_BASE_HREF } from '@angular/common';
import { enableProdMode } from '@angular/core';
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';
import { createServerRenderer } from 'aspnet-prerendering';
export { AppServerModule } from './app/app.server.module';
enableProdMode();
const options = {
document: params.data.originalHtml,
url: params.url,
extraProviders: [
provideModuleMap(LAZY_MODULE_MAP),
{ provide: APP_BASE_HREF, useValue: params.baseUrl },
{ provide: 'BASE_URL', useValue: params.origin + params.baseUrl }
]
};
El código de este archivo es lo que ejecuta ASP.NET Core en cada solicitud cuando ejecuta el middleware
UseSpaPrerendering que agregó a la clase Startup. Se encarga de recibir params del código de .NET (como la
dirección URL que se solicita), y realiza llamadas a las API de SSR de Angular para obtener el HTML resultante.
Estrictamente hablando, esto es suficiente para habilitar SSR en modo de desarrollo. Para que la aplicación
funcione correctamente cuando se publica, es fundamental realizar un cambio final. En el archivo .csproj principal
de la aplicación, establezca el valor de propiedad BuildServerSideRenderer en true :
Este valor configura el proceso de compilación para ejecutar build:ssr durante la publicación e implementar los
archivos SSR en el servidor. Si no se habilita, SSR produce un error en producción.
Cuando la aplicación se ejecuta en modo de desarrollo o de producción, el código de Angular se representa
previamente como HTML en el servidor. El código del lado cliente se ejecuta con normalidad.
Paso de datos del código de .NET al código de TypeScript
Durante SSR, quizás quiera puede pasar datos por solicitud de la aplicación ASP.NET Core a la aplicación de
Angular. Por ejemplo, podría pasar información de cookies o algo que se haya leído de una base de datos. Para
ello, edite la clase Startup. En la devolución de llamada de UseSpaPrerendering , establezca un valor para
options.SupplyData , como el siguiente:
NOTE
Esta documentación no trata sobre la plantilla de proyecto de React incluida en ASP.NET Core 2.0. Trata sobre la nueva
plantilla de React que puede actualizar manualmente. La plantilla se incluye de forma predeterminada en ASP.NET Core 2.1.
La plantilla de proyecto actualizada de React ofrece un práctico punto de partida para las aplicaciones ASP.NET
Core que usan React y las convenciones create-react-app (CRA) para implementar una completa interfaz de
usuario (UI) en el lado cliente.
La plantilla es equivalente a crear un proyecto de ASP.NET Core para que funcione como un back-end de API y un
proyecto de React de CRA estándar para que funcione como interfaz de usuario, pero con la comodidad de
hospedar ambos en un único proyecto de aplicación que se puede compilar y publicar como una sola unidad.
cd ClientApp
npm install --save <package_name>
Publicación e implementación
En el desarrollo, la aplicación se ejecuta en modo optimizado para comodidad del desarrollador. Por ejemplo,
agrupaciones de JavaScript contienen asignaciones de origen (de modo que durante la depuración, puede ver el
código fuente original). La aplicación inspecciona los cambios en los archivos de JavaScript, HTML y CSS en el
disco y, automáticamente, realiza una nueva compilación y recarga cuando observa que esos archivos han
cambiado.
En producción, use una versión de la aplicación que esté optimizada para el rendimiento. Esto se configura para
que tenga lugar automáticamente. Al publicar, la configuración de compilación emite una compilación transpilada
reducida del código de cliente. A diferencia de la compilación de desarrollo, la compilación de producción no
requiere la instalación de Node.js en el servidor.
Puede usar métodos de implementación y hospedaje de ASP.NET Core estándar.
cd ClientApp
npm start
2. Modifique la aplicación ASP.NET Core para usar la instancia del servidor de CRA externo en lugar de
iniciar una de las suyas. En la clase Startup, reemplace la invocación de spa.UseReactDevelopmentServer por
lo siguiente:
spa.UseProxyToSpaDevelopmentServer("http://localhost:3000");
Cuando inicie la aplicación ASP.NET Core, no se iniciará un servidor de CRA. En su lugar, se usa la instancia que
inició manualmente. Esto le permite iniciar y reiniciar con mayor rapidez. Ya no tiene que esperar a que la
aplicación de React se recompile de una vez a otra.
Uso de la plantilla de proyecto React-with-Redux con
ASP.NET Core
22/06/2018 • 2 minutes to read • Edit Online
NOTE
Esta documentación no trata sobre la plantilla de proyecto de React-with-Redux incluida en ASP.NET Core 2.0. Trata sobre la
nueva plantilla de React-with-Redux que puede actualizar manualmente. La plantilla se incluye de forma predeterminada en
ASP.NET Core 2.1.
La plantilla de proyecto actualizada de React-with-Redux ofrece un práctico punto de partida para las aplicaciones
ASP.NET Core que usan React, Redux y las convenciones create-react-app (CRA) para implementar una completa
interfaz de usuario (UI) en el lado cliente.
A excepción del comando de creación del proyecto, toda la información sobre la plantilla de React-with-Redux es
igual que la de la plantilla de React. Para crear este tipo de proyecto, ejecute dotnet new reactredux en lugar de
dotnet new react . Para más información sobre la funcionalidad común a ambas plantillas basadas en React,
consulte la documentación de la plantilla de React.
SignalR de ASP.NET Core
25/06/2018 • 2 minutes to read • Edit Online
Introducción
Introducción
Concentradores
Cliente de JavaScript
Cliente .NET
HubContext
Usuarios y grupos
Protocolo de concentrador MessagePack
Publicar en Azure
Streaming
Plataformas compatibles
Introducción a ASP.NET Core SignalR
22/06/2018 • 4 minutes to read • Edit Online
¿Qué es SignalR?
Núcleo de ASP.NET SignalR es una biblioteca que simplifica la agregación de funcionalidades de web en tiempo
real a las aplicaciones. La funcionalidad web en tiempo real permite que el código de servidor para el contenido de
inserción a los clientes al instante.
Buenos candidatos para SignalR:
Aplicaciones que requieren alta frecuencia actualizaciones desde el servidor. Se trata de juegos, redes sociales,
votar, subasta, mapas y aplicaciones GPS.
Los paneles y supervisión de aplicaciones. Los ejemplos incluyen paneles de la empresa, las actualizaciones de
ventas instantáneas, o alertas de viaje.
Aplicaciones de colaboración. Aplicaciones de pizarra y reunión software de equipo son ejemplos de
aplicaciones de colaboración.
Aplicaciones que requieren las notificaciones. Las redes sociales, correo electrónico, chat, juegos, alertas de
viaje y muchas otras aplicaciones utilizan notificaciones.
SignalR proporciona una API para la creación de servidor a cliente llamadas a procedimiento remoto (RPC ). Las
RPC llamar a funciones de JavaScript en los clientes desde el código de .NET Core en el servidor de.
SignalR principales de ASP.NET:
Controla automáticamente la administración de conexiones.
Permite difundir mensajes a todos los clientes conectados al mismo tiempo. Por ejemplo, un salón de chat.
Habilita el envío de mensajes a clientes específicos o grupos de clientes.
Es código abierto en GitHub.
Escalable.
La conexión entre el cliente y el servidor es persistente, a diferencia de una conexión HTTP.
Transportes
Resúmenes de SignalR a través de una serie de técnicas para crear aplicaciones web en tiempo real. WebSockets
es el transporte óptimo, pero se pueden usar otras técnicas como eventos de Server-Sent y sondeo largo cuando
los que no están disponibles. SignalR detectará automáticamente e inicializar el transporte adecuado en función de
las características admitidas en el servidor y el cliente.
Concentradores
SignalR usa centros para la comunicación entre clientes y servidores.
Un concentrador es una canalización de alto nivel que permite que el cliente y servidor para llamar a métodos en
entre sí. SignalR administra el envío a través de los límites del equipo automáticamente, permitiendo a los clientes
llamar a métodos en el servidor como fácilmente como métodos locales y viceversa. Los concentradores permiten
pasar parámetros fuertemente tipados a métodos, lo que permite el enlace de modelos. SignalR proporciona dos
protocolos de concentrador integrada: un protocolo de texto en función de JSON y un protocolo binario basado en
MessagePack. Por lo general, MessagePack crea mensajes más pequeños que cuando se usan JSON. Deben ser
compatible con los exploradores más antiguos nivel XHR 2 para proporcionar compatibilidad con el protocolo
MessagePack.
Los centros de llamar a código de cliente mediante el envío de mensajes mediante el transporte activo. Los
mensajes contienen el nombre y los parámetros del método de cliente. Objetos que se envíen como parámetros de
método se deserializan mediante el protocolo configurado. El cliente intenta hacer coincidir el nombre a un
método en el código de cliente. Cuando se produce una coincidencia, el método de cliente se ejecuta con los datos
del parámetro deserializado.
Recursos adicionales
Empezar a trabajar con SignalR para ASP.NET Core
Plataformas compatibles
Concentradores
Cliente de JavaScript
Use los concentradores de SignalR para ASP.NET
Core
22/06/2018 • 6 minutes to read • Edit Online
services.AddSignalR();
Al agregar la funcionalidad de SignalR a una aplicación de ASP.NET Core, el programa de instalación SignalR
rutas mediante una llamada a app.UseSignalR en el Startup.Configure método.
app.UseSignalR(route =>
{
route.MapHub<ChatHub>("/chathub");
});
Puede especificar un tipo de valor devuelto y parámetros, incluidos los tipos complejos y matrices, como lo haría
en cualquier método de C#. SignalR controla la serialización y deserialización de objetos complejos y matrices en
los parámetros y valores devueltos.
PROPERTY DESCRIPCIÓN
MÉTODO DESCRIPCIÓN
Cada propiedad o método de las tablas anteriores devuelve un objeto con un SendAsync método. El SendAsync
método le permite especificar el nombre y los parámetros del método de cliente para llamar a.
Controlar errores
Las excepciones producidas en los métodos de concentrador se envían al cliente que invocó el método. En el
cliente de JavaScript, el invoke método devuelve un compromiso de JavaScript. Cuando el cliente recibe un error
con un controlador asociado a la promesa mediante catch , se invoca y se pasan como JavaScript Error objeto.
Recursos relacionados
Introducción a ASP.NET Core SignalR
Cliente de JavaScript
Publicar en Azure
Cliente de SignalR JavaScript Core ASP.NET
22/06/2018 • 6 minutes to read • Edit Online
npm init -y
npm install @aspnet/signalr
NPM instala el contenido del paquete en el *node_modules\ @aspnet\signalr\dist\browser* carpeta. Cree una
carpeta nueva denominada signalr en el wwwroot\lib carpeta. Copia la signalr.js del archivo a la
wwwroot\lib\signalr carpeta.
<script src="~/lib/signalr/signalr.js"></script>
Conectarse a un concentrador
El código siguiente se crea e inicia una conexión. Nombre del concentrador distingue mayúsculas de minúsculas.
namespace SignalRChat
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
services.AddMvc();
services.AddSignalR();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseCors("CorsPolicy");
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/chathub");
});
app.UseMvc();
}
}
}
Llamar a métodos de concentrador de cliente
Los clientes de JavaScript llama al métodos públicos en concentradores por usando connection.invoke . El
invoke método acepta dos argumentos:
El nombre del método de concentrador. En el ejemplo siguiente, el nombre de la base de datos central es
SendMessage .
Los argumentos definidos en el método de concentrador. En el ejemplo siguiente, el nombre del argumento es
message .
El código anterior en connection.on se ejecuta cuando llama a código del lado servidor mediante el SendAsync
método.
SignalR determina a qué método de cliente para llamar a haciendo coincidir el nombre de método y los
argumentos definan en SendAsync y connection.on .
NOTE
Como práctica recomendada, llame a connection.start después connection.on por lo que los controladores están
registrados antes de que se reciben los mensajes.
Configurar la traza de registro del lado cliente pasando un registrador y el tipo de evento que se registran cuando
se realiza la conexión. Los mensajes se registran con el nivel de registro especificado y versiones posteriores.
Niveles de registro disponibles son los siguientes:
signalR.LogLevel.Error : Mensajes de error. Registros Error sólo mensajes.
signalR.LogLevel.Warning : Mensajes de advertencia acerca de posibles errores. Registros de Warning , y
Error mensajes.
signalR.LogLevel.Information : Mensajes de estado sin errores. Registros de Information , Warning , y Error
mensajes.
signalR.LogLevel.Trace : Mensajes de seguimiento. Registra todo, incluidos los datos transportados entre
cliente y concentrador.
Use la configureLogging método en HubConnectionBuilder para configurar el nivel de registro. Los mensajes se
registran en la consola del explorador.
Recursos relacionados
Concentradores
Cliente .NET
Publicar en Azure
Permitir solicitudes entre orígenes (CORS ) en ASP.NET Core
Cliente de .NET Core de ASP.NET SignalR
22/06/2018 • 3 minutes to read • Edit Online
Install-Package Microsoft.AspNetCore.SignalR.Client
Conectarse a un concentrador
Para establecer una conexión, cree un HubConnectionBuilder y llame a Build . La dirección URL del concentrador,
protocolo, tipo de transporte, nivel de registro, encabezados y otras opciones se pueden configurar durante la
creación de una conexión. Configurar las opciones necesarias mediante la inserción de cualquiera de los
HubConnectionBuilder métodos en Build . Iniciar la conexión con StartAsync .
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Logging;
using System;
using System.Windows;
namespace SignalRChatClient
{
public partial class MainWindow : Window
{
HubConnection connection;
public MainWindow()
{
InitializeComponent();
try
{
await connection.StartAsync();
messagesList.Items.Add("Connection started");
connectButton.IsEnabled = false;
sendButton.IsEnabled = true;
}
catch (Exception ex)
{
messagesList.Items.Add(ex.Message);
}
}
El código anterior en connection.On se ejecuta cuando llama a código del lado servidor mediante el SendAsync
método.
try
{
await connection.InvokeAsync("SendMessage",
userTextBox.Text, messageTextBox.Text);
}
catch (Exception ex)
{
messagesList.Items.Add(ex.Message);
}
Recursos adicionales
Concentradores
Cliente de JavaScript
Publicar en Azure
Enviar mensajes desde fuera de un concentrador
22/06/2018 • 2 minutes to read • Edit Online
NOTE
Esto difiere de ASP.NET SignalR que utiliza GlobalHost para proporcionar acceso a la IHubContext . ASP.NET Core tiene un
marco de inyección de dependencia que elimina la necesidad de este singleton global.
Ahora, con acceso a una instancia de IHubContext , puede llamar a métodos de concentrador como si estuviera en
el concentrador de sí mismo.
NOTE
Cuando se llaman a métodos de concentrador desde fuera de la Hub clase, no hay ningún autor de llamada asociado a la
invocación. Por lo tanto, no hay ningún acceso a la ConnectionId , Caller , y Others propiedades.
Recursos relacionados
Introducción
Concentradores
Publicar en Azure
Administrar usuarios y grupos de SignalR
22/06/2018 • 3 minutes to read • Edit Online
Usuarios de SignalR
SignalR le permite enviar mensajes a todas las conexiones asociadas a un usuario específico. De forma
predeterminada se usa SignalR el ClaimTypes.NameIdentifier desde el ClaimsPrincipal asociado a la conexión
como el identificador de usuario. Un único usuario puede tener varias conexiones a una aplicación de SignalR. Por
ejemplo, un usuario podría estar conectado en el escritorio, así como el número de teléfono. Cada dispositivo tiene
una conexión SignalR independiente, pero están asociadas con el mismo usuario. Si se envía un mensaje al
usuario, todas las conexiones asociadas a ese usuario recibe el mensaje.
Enviar un mensaje a un usuario específico, pase el identificador de usuario para el User funcionando en su
método de concentrador, tal como se muestra en el ejemplo siguiente:
NOTE
El identificador de usuario distingue mayúsculas de minúsculas.
services.AddSingleton<IUserIdProvider, CustomUserIdProvider>();
}
NOTE
AddSignalR debe llamarse antes de registrar los servicios SignalR personalizados.
Grupos de SignalR
Un grupo es una colección de conexiones asociadas a un nombre. Los mensajes pueden enviarse a todas las
conexiones en un grupo. Los grupos son el método recomendado para enviar a una conexión o varias conexiones
porque los grupos administrados por la aplicación. Una conexión puede ser un miembro de varios grupos. Esto
hace que los grupos ideal para algo parecido a una aplicación de chat, donde cada habitación puede representarse
como un grupo. Las conexiones se pueden agregar a o quitar de los grupos a través de la AddToGroupAsync y
RemoveFromGroupAsync métodos.
Pertenencia a grupos no se conserva cuando se vuelve a conectar una conexión. Debe volver a unirse al grupo
cuando se vuelve a establecer la conexión. No es posible contar a los miembros de un grupo, puesto que esta
información no está disponible si la aplicación se escala a varios servidores.
NOTE
Nombres de grupo distinguen entre mayúsculas y minúsculas.
Recursos relacionados
Introducción
Concentradores
Publicar en Azure
Utilice el protocolo de concentrador de MessagePack
en SignalR para ASP.NET Core
22/06/2018 • 4 minutes to read • Edit Online
¿Qué es MessagePack?
MessagePack es un formato de serialización binaria es rápido y compacto. Es útil cuando el rendimiento y el ancho
de banda son un problema porque crea mensajes más pequeños en comparación con JSON. Dado que es un
formato binario, mensajes no son legibles cuando se examinan los registros y seguimientos de red a menos que
los bytes se pasan a través de un analizador de MessagePack. SignalR tiene compatibilidad integrada para el
formato MessagePack y proporciona las API para el cliente y el servidor usar.
NOTE
JSON está habilitada de forma predeterminada. Agregar MessagePack habilita la compatibilidad para los clientes de JSON y
MessagePack.
services.AddSignalR()
.AddMessagePackProtocol();
Para personalizar cómo MessagePack dará formato a los datos, AddMessagePackProtocol toma un delegado para
configurar las opciones. En ese delegado, el FormatterResolvers propiedad puede utilizarse para configurar las
opciones de serialización MessagePack. Para obtener más información sobre cómo funcionan los solucionadores,
visite la biblioteca de MessagePack en MessagePack CSharp. Los atributos se pueden utilizar en los objetos que
desea serializar para definir cómo debe controlarse.
services.AddSignalR()
.AddMessagePackProtocol(options =>
{
options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
{
MessagePack.Resolvers.StandardResolver.Instance
};
});
NOTE
Esto AddMessagePackProtocol llamada toma un delegado para configurar opciones como el servidor.
Cliente de JavaScript
MessagePack para el cliente de Javascript se admiten por los @aspnet/signalr-protocol-msgpack paquete NPM.
Después de instalar el paquete npm, el módulo se puede usar directamente mediante un cargador de módulos de
JavaScript o importarse en el explorador haciendo referencia a la *node_modules\ @aspnet\signalr-protocol-
msgpack\dist\browser\signalr-protocol-msgpack.js* archivo. En un explorador la msgpack5 también se debe hacer
referencia a la biblioteca. Use un <script> etiqueta que se va a crear una referencia. La biblioteca se puede
encontrar en node_modules\msgpack5\dist\msgpack5.js.
NOTE
Cuando se usa el <script> elemento, el orden es importante. Si msgpack.js de protocolo de signalr se hace referencia
antes de msgpack5.js, se produce un error al intentar conectarse con MessagePack. signalr.js también es necesaria antes de
msgpack.js de protocolo de signalr.
<script src="~/lib/signalr/signalr.js"></script>
<script src="~/lib/msgpack5/msgpack5.js"></script>
<script src="~/lib/signalr/signalr-protocol-msgpack.js"></script>
NOTE
En este momento, no hay ninguna opción de configuración para el protocolo MessagePack en el cliente de JavaScript.
Recursos relacionados
Primeros pasos
Cliente .NET
Cliente de JavaScript
Publicar un núcleo de ASP.NET SignalR aplicación a
una aplicación Web de Azure
22/06/2018 • 3 minutes to read • Edit Online
Aplicación Web de Azure es un Microsoft la informática en nube servicio de plataforma para hospedar las
aplicaciones web, incluido ASP.NET Core.
NOTE
En este artículo se refiere a la publicación de una aplicación de ASP.NET Core SignalR desde Visual Studio. Visite SignalR
servicio para Azure para obtener más información sobre el uso de SignalR en Azure.
Publicar la aplicación
Visual Studio proporciona herramientas integradas para la publicación en una aplicación Web de Azure. Puede
usar el usuario de Visual Studio Code CLI de Azure comandos para publicar aplicaciones para Azure. Este
artículo trata la publicación con las herramientas de Visual Studio. Para publicar una aplicación mediante la CLI
de Azure, consulte publicar una aplicación de ASP.NET Core en Azure con las herramientas de línea de
comandos.
Haga doble clic en el proyecto en el Explorador de soluciones y seleccione publicar. Confirme que crear
nuevo está activada en la elegir un destino de publicación cuadro de diálogo y seleccione publicar.
Escriba la siguiente información en el crear servicio en la aplicación cuadro de diálogo y seleccione crear.
ELEMENTO DESCRIPCIÓN
Si se produce un error de HTTP 502.2, consulte versión de vista previa de implementar ASP.NET Core para el
servicio de aplicaciones de Azure para resolverlo.
Recursos relacionados
Publicar una aplicación de ASP.NET Core en Azure con las herramientas de línea de comandos
Publicar una aplicación de ASP.NET Core en Azure con Visual Studio
Hospedar e implementar aplicaciones de ASP.NET Core Preview en Azure
Uso de ASP.NET Core SignalR transmisión por
secuencias
23/06/2018 • 4 minutes to read • Edit Online
Configurar el concentrador
Un método de concentrador se convierte automáticamente en un método de concentrador de transmisión por
secuencias cuando devuelve un ChannelReader<T> o Task<ChannelReader<T>> . A continuación se muestra un
ejemplo que muestra los conceptos básicos de transmisión de datos al cliente. Cada vez que un objeto se escribe
en el ChannelReader ese objeto se envía inmediatamente al cliente. Al final, el ChannelReader se complete para
indicar al cliente la secuencia está cerrada.
NOTE
Escribir en el ChannelReader en un subproceso en segundo plano y vuelva la ChannelReader tan pronto como sea
posible. Otras las invocaciones del concentrador se bloqueará hasta que un ChannelReader se devuelve.
return channel.Reader;
}
writer.TryComplete();
}
}
Cliente .NET
El StreamAsChannelAsync método HubConnection se utiliza para invocar un método de transmisión por secuencias.
Pasar el nombre del método de concentrador y los argumentos definidos en el método de concentrador a
StreamAsChannelAsync . El parámetro genérico en StreamAsChannelAsync<T> especifica el tipo de objetos devueltos
por el método de transmisión por secuencias. Un ChannelReader<T> se devuelve desde la invocación de flujo y
representa el flujo en el cliente. Para leer los datos, es un patrón común para recorrer en WaitToReadAsync y llamar
a TryRead cuando los datos están disponibles. El bucle terminará cuando se ha cerrado la secuencia por el
servidor, o el token de cancelación que se pasa a StreamAsChannelAsync se cancela.
Console.WriteLine("Streaming completed");
Cliente de JavaScript
Los clientes de JavaScript llamar a métodos de transmisión por secuencias en concentradores mediante
connection.stream . El stream método acepta dos argumentos:
Para finalizar la secuencia de la llamada del cliente la dispose método en el ISubscription que se devuelve desde
el subscribe método.
Recursos relacionados
Concentradores
Cliente .NET
Cliente de JavaScript
Publicar en Azure
Plataformas compatibles con ASP.NET Core SignalR
22/06/2018 • 2 minutes to read • Edit Online
EXPLORADOR VERSIÓN
Al pulsar un elemento en la pantalla de la lista principal se abre un cuadro de diálogo de edición, donde se puede
modificar el nombre del elemento, las notas y la configuración de Done (Listo), o se puede eliminar el elemento:
Este ejemplo está configurado para usar de forma predeterminada servicios back-end hospedados en
developer.xamarin.com, lo que permite operaciones de solo lectura. Para probarlo usted mismo con la aplicación
de ASP.NET Core que creó en la siguiente sección que se ejecuta en el equipo, debe actualizar la constante
RestUrl de la aplicación. Vaya hasta el proyecto ToDoREST y abra el archivo Constants.cs. Reemplace RestUrl con
una dirección URL que incluya la dirección IP de su equipo (no localhost ni 127.0.0.1, puesto que esta dirección se
usa desde el emulador de dispositivo, no desde el equipo). Incluya también el número de puerto (5000). Para
comprobar que los servicios funcionan con un dispositivo, asegúrese de que no tiene un firewall activo que
bloquea el acceso a este puerto.
NOTE
Asegúrese de que ejecuta la aplicación directamente, en lugar de tras IIS Express, que omite las solicitudes no locales de
forma predeterminada. Ejecute dotnet run desde un símbolo del sistema o elija el perfil del nombre de aplicación en la lista
desplegable de destino de depuración en la barra de herramientas de Visual Studio.
Agregue una clase de modelo para representar las tareas pendientes. Marque los campos obligatorios mediante el
atributo [Required] :
using System.ComponentModel.DataAnnotations;
namespace ToDoApi.Models
{
public class ToDoItem
{
[Required]
public string ID { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Notes { get; set; }
Los métodos de API necesitan alguna manera de trabajar con los datos. Use la misma interfaz de
IToDoRepository que usa el ejemplo original de Xamarin:
using System.Collections.Generic;
using ToDoApi.Models;
namespace ToDoApi.Interfaces
{
public interface IToDoRepository
{
bool DoesItemExist(string id);
IEnumerable<ToDoItem> All { get; }
ToDoItem Find(string id);
void Insert(ToDoItem item);
void Update(ToDoItem item);
void Delete(string id);
}
}
using System.Collections.Generic;
using System.Linq;
using ToDoApi.Interfaces;
using ToDoApi.Models;
namespace ToDoApi.Services
{
public class ToDoRepository : IToDoRepository
{
private List<ToDoItem> _toDoList;
public ToDoRepository()
{
InitializeData();
}
_toDoList.Add(todoItem1);
_toDoList.Add(todoItem2);
_toDoList.Add(todoItem3);
}
}
}
services.AddSingleton<IToDoRepository,ToDoRepository>();
}
TIP
Obtenga más información sobre cómo crear API web en Cree su primera API web con ASP.NET Core MVC y Visual Studio.
Crear el controlador
Agregue un nuevo controlador para el proyecto: ToDoItemsController. Debe heredar de
Microsoft.AspNetCore.Mvc.Controller. Agregue un atributo Route para indicar que el controlador controlará las
solicitudes realizadas a las rutas de acceso que comiencen con api/todoitems . El token [controller] de la ruta se
sustituye por el nombre del controlador (si se omite el sufijo Controller ) y es especialmente útil para las rutas
globales. Obtenga más información sobre el enrutamiento.
El controlador necesita un IToDoRepository para funcionar. Solicite una instancia de este tipo a través del
constructor del controlador. En tiempo de ejecución, esta instancia se proporcionará con la compatibilidad del
marco con la inserción de dependencias.
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using ToDoApi.Interfaces;
using ToDoApi.Models;
namespace ToDoApi.Controllers
{
[Route("api/[controller]")]
public class ToDoItemsController : Controller
{
private readonly IToDoRepository _toDoRepository;
Esta API es compatible con cuatro verbos HTTP diferentes para realizar operaciones CRUD (creación, lectura,
actualización, eliminación) en el origen de datos. La más simple de ellas es la operación de lectura, que
corresponde a una solicitud HTTP GET.
Leer elementos
La solicitud de una lista de elementos se realiza con una solicitud GET al método List . El atributo [HttpGet] en
el método List indica que esta acción solo debe controlar las solicitudes GET. La ruta de esta acción es la ruta
especificada en el controlador. No es necesario usar el nombre de acción como parte de la ruta. Solo debe
asegurarse de que cada acción tiene una ruta única e inequívoca. El enrutamiento de atributos se puede aplicar
tanto a los niveles de controlador como de método para crear rutas específicas.
[HttpGet]
public IActionResult List()
{
return Ok(_toDoRepository.All);
}
El método List devuelve un código de respuesta 200 OK y todos los elementos de lista de tareas, serializados
como JSON.
Puede probar el nuevo método de API con una variedad de herramientas, como Postman, que se muestra a
continuación:
Crear elementos
Por convención, la creación de elementos de datos se asigna al verbo HTTP POST. El método Create tiene un
atributo [HttpPost] aplicado y acepta una instancia ToDoItem . Puesto que el argumento item se pasará en el
cuerpo de la solicitud POST, este parámetro se decora con el atributo [FromBody] .
Dentro del método, se comprueba la validez del elemento y si existió anteriormente en el almacén de datos y, si no
hay problemas, se agrega mediante el repositorio. Al comprobar ModelState.IsValid se realiza una validación de
modelos, y debe realizarse en cada método de API que acepte datos proporcionados por usuario.
[HttpPost]
public IActionResult Create([FromBody] ToDoItem item)
{
try
{
if (item == null || !ModelState.IsValid)
{
return BadRequest(ErrorCode.TodoItemNameAndNotesRequired.ToString());
}
bool itemExists = _toDoRepository.DoesItemExist(item.ID);
if (itemExists)
{
return StatusCode(StatusCodes.Status409Conflict, ErrorCode.TodoItemIDInUse.ToString());
}
_toDoRepository.Insert(item);
}
catch (Exception)
{
return BadRequest(ErrorCode.CouldNotCreateItem.ToString());
}
return Ok(item);
}
El ejemplo usa una enumeración que contiene códigos de error que se pasan al cliente móvil:
Pruebe a agregar nuevos elementos con Postman eligiendo el verbo POST que proporciona el nuevo objeto en
formato JSON en el cuerpo de la solicitud. También debe agregar un encabezado de solicitud que especifica un
Content-Type de application/json .
El método devuelve el elemento recién creado en la respuesta.
Actualizar elementos
La modificación de registros se realiza mediante solicitudes HTTP PUT. Aparte de este cambio, el método Edit es
casi idéntico a Create . Tenga en cuenta que, si no se encuentra el registro, la acción Edit devolverá una
respuesta NotFound (404).
[HttpPut]
public IActionResult Edit([FromBody] ToDoItem item)
{
try
{
if (item == null || !ModelState.IsValid)
{
return BadRequest(ErrorCode.TodoItemNameAndNotesRequired.ToString());
}
var existingItem = _toDoRepository.Find(item.ID);
if (existingItem == null)
{
return NotFound(ErrorCode.RecordNotFound.ToString());
}
_toDoRepository.Update(item);
}
catch (Exception)
{
return BadRequest(ErrorCode.CouldNotUpdateItem.ToString());
}
return NoContent();
}
Para probar con Postman, cambie el verbo a PUT. Especifique los datos actualizados del objeto en el cuerpo de la
solicitud.
Este método devuelve una respuesta NoContent (204) cuando se realiza correctamente, para mantener la
coherencia con la API existente.
Eliminar elementos
La eliminación de registros se consigue mediante solicitudes DELETE al servicio y pasando el identificador del
elemento que va a eliminar. Al igual que con las actualizaciones, las solicitudes para elementos que no existen
recibirán respuestas NotFound . De lo contrario, las solicitudes correctas recibirán una respuesta NoContent (204).
[HttpDelete("{id}")]
public IActionResult Delete(string id)
{
try
{
var item = _toDoRepository.Find(id);
if (item == null)
{
return NotFound(ErrorCode.RecordNotFound.ToString());
}
_toDoRepository.Delete(id);
}
catch (Exception)
{
return BadRequest(ErrorCode.CouldNotDeleteItem.ToString());
}
return NoContent();
}
Tenga en cuenta que, al probar la funcionalidad de eliminar, no se necesita nada en el cuerpo de la solicitud.
Publicación en Azure
Vea Publicar una aplicación web de ASP.NET Core en Azure App Service con Visual Studio para obtener
instrucciones sobre cómo publicar una aplicación en Azure con Visual Studio. También se puede publicar la
aplicación en Azure desde la línea de comandos.
Recursos adicionales
Para información sobre cómo usar Docker como entorno de hospedaje, vea Host ASP.NET Core apps in
Docker (Hospedar aplicaciones de ASP.NET Core en Docker).
Hospedaje de ASP.NET Core en Azure App Service
10/05/2018 • 12 minutes to read • Edit Online
Azure App Service es un servicio de plataforma de informática en la nube de Microsoft que sirve para hospedar
aplicaciones web, como ASP.NET Core.
Recursos útiles
La documentación de Web Apps de Azure es un recurso que incluye documentación, tutoriales, ejemplos, guías de
procedimientos y otros recursos de aplicaciones de Azure. Dos tutoriales importantes que pertenecen al
hospedaje de aplicaciones de ASP.NET Core son:
Inicio rápido: Crear una aplicación web de ASP.NET Core en Azure
Usar Visual Studio para crear e implementar una aplicación web de ASP.NET Core para Azure App Service en
Windows.
Inicio rápido: Crear una aplicación web de .NET Core en App Service en Linux
Usar la línea de comandos para crear e implementar una aplicación web de ASP.NET Core para Azure App Service
en Linux.
Los artículos siguientes están disponibles en la documentación de ASP.NET Core:
Publicación en Azure con Visual Studio
Obtenga información sobre cómo publicar una aplicación de ASP.NET Core en Azure App Service con Visual
Studio.
Publicación en Azure con herramientas de CLI
Obtenga información sobre cómo publicar una aplicación de ASP.NET Core en Azure App Service con el cliente de
línea de comandos de Git.
Implementación continua en Azure con Visual Studio y Git
Obtenga información sobre cómo crear una aplicación web de ASP.NET Core con Visual Studio e implementarla
en Azure App Service con Git para una implementación continua.
Implementación continua en Azure con VSTS
Configure una compilación de integración continua para una aplicación de ASP.NET Core y, después, cree una
versión de implementación continua para Azure App Service.
Espacio aislado de Azure Web App
Detecte limitaciones de ejecución en tiempo de ejecución de Azure App Service aplicadas por la plataforma de
aplicaciones de Azure.
Configuración de aplicación
En ASP.NET Core 2.0 y versiones posteriores, tres paquetes del metapaquete Microsoft.AspNetCore.All
proporcionan características de registro automático para aplicaciones implementadas en Azure App Service:
Microsoft.AspNetCore.AzureAppServices.HostingStartup utiliza IHostingStartup para proporcionar a ASP.NET
Core integración iluminada con Azure App Service. El paquete
Microsoft.AspNetCore.AzureAppServicesIntegration proporciona las características de registro agregadas.
Microsoft.AspNetCore.AzureAppServicesIntegration ejecuta AddAzureWebAppDiagnostics para agregar a
Azure App Service proveedores de registro de diagnósticos en el paquete
Microsoft.Extensions.Logging.AzureAppServices.
Microsoft.Extensions.Logging.AzureAppServices proporciona implementaciones de registrador para admitir
registros de diagnósticos de Azure App Service y características de transmisión en secuencias de registro.
Supervisión y registro
Para obtener información sobre supervisión, registro y solución de problemas, consulte los artículos siguientes:
Cómo: Supervisar aplicaciones en Azure App Service
Obtenga información sobre cómo revisar las cuotas y las métricas para las aplicaciones y los planes de App
Service.
Habilitar el registro de diagnósticos para las aplicaciones web en Azure App Service
Descubra cómo habilitar y acceder a registro de diagnóstico para los códigos de estado HTTP, solicitudes con error
y actividad del servidor web.
Introducción a control de errores en ASP.NET Core
Comprender los métodos comunes para controlar los errores en las aplicaciones de ASP.NET Core.
Solución de problemas de ASP.NET Core en Azure App Service
Obtenga información sobre cómo diagnosticar problemas con las implementaciones de Azure App Service con las
aplicaciones de ASP.NET Core.
Referencia de errores comunes de Azure App Service e IIS con ASP.NET Core
Consulte los errores comunes de configuración de implementación para las aplicaciones hospedadas por Azure
App Service/IIS con consejos de solución de problemas.
Seleccione ASP.NET Core 2.1 (x86) Runtime o ASP.NET Core 2.1 (x64) Runtime.
Seleccione Aceptar. Vuelva a seleccionar Aceptar.
Cuando se complete la operación de adición, se instalará la versión preliminar de .NET Core 2.1. Para comprobar
la instalación, ejecute dotnet --info en la consola. En la hoja de App Service:
Escriba "con" en el cuadro de búsqueda.
Seleccione Consola.
Escriba dotnet --info en la consola.
La imagen anterior se capturó en el momento en que se escribía esto. Podría ver una versión diferente.
dotnet --info muestra la ruta de acceso a la extensión de sitio en la que se ha instalado la versión preliminar.
Muestra que la aplicación se está ejecutando desde la extensión de sitio, en lugar de desde la ubicación
predeterminada ProgramFiles. Si ve ProgramFiles, reinicie el sitio y ejecute dotnet --info .
Uso de la extensión de sitio de versión preliminar con una plantilla de ARM
Si usa una plantilla de ARM para crear e implementar aplicaciones, puede usar el tipo de recurso siteextensions
para agregar la extensión de sitio a una aplicación web. Por ejemplo:
{
"type": "siteextensions",
"name": "AspNetCoreRuntime",
"apiVersion": "2015-04-01",
"location": "[resourceGroup().location]",
"properties": {
"version": "[parameters('aspnetcoreVersion')]"
},
"dependsOn": [
"[resourceId('Microsoft.Web/Sites', parameters('siteName'))]"
]
}
Recursos adicionales
Introducción a Web Apps (vídeo de introducción de 5 minutos)
Azure App Service: el mejor lugar para hospedar las aplicaciones .NET (vídeo de introducción de 55 minutos)
Azure Friday: experiencia de diagnóstico y solución de problemas de Azure App Service (vídeo de 12 minutos)
Introducción a diagnósticos de Azure App Service
Azure App Service en Windows Server utiliza Internet Information Services (IIS ). Los temas siguientes se aplican
a la tecnología subyacente de IIS:
Hospedaje de ASP.NET Core en Windows con IIS
Introducción al módulo ASP.NET Core
Referencia de configuración del módulo ASP.NET Core
Módulos de IIS con ASP.NET Core
Biblioteca de TechNet de Microsoft: Windows Server
Publicar una aplicación de ASP.NET Core en Azure
con Visual Studio
25/06/2018 • 7 minutes to read • Edit Online
IMPORTANT
Aviso sobre el uso de las versiones preliminares de ASP.NET Core 2.1
Consulte Implementar una versión preliminar de ASP.NET Core en Azure App Service.
Vea Publish to Azure from Visual Studio for Mac (Publicación en Azure desde Visual Studio para Mac) si
trabaja en un equipo macOS.
Para solucionar un problema con la implementación de App Service, vea Troubleshoot ASP.NET Core on Azure
App Service (Solucionar problemas de ASP.NET Core en Azure App Service).
Configurar
Abra una cuenta gratuita de Azure si no tiene una.
Ejecutar la aplicación
Presione CTRL+F5 para ejecutar el proyecto.
Pruebe los vínculos Acerca de y Contacto.
Registrar un usuario
Seleccione Registrar y registre a un usuario nuevo. Puede usar una dirección de correo electrónico
ficticia. Al enviar la información, la página mostrará el error siguiente:
"Error interno del servidor: Error en una operación de base de datos al procesar la solicitud. Excepción
de SQL: No se puede abrir la base de datos. La aplicación de las migraciones existentes para el contexto
de base de datos de la aplicación puede solucionar este problema".
Seleccione Aplicar migraciones y, una vez actualizada la página, vuelva a cargarla.
La aplicación muestra el correo electrónico usado para registrar al nuevo usuario y el vínculo Cerrar sesión.
Implementación de la aplicación en Azure
Desde el Explorador de soluciones, haga clic con el botón derecho en el proyecto y seleccione Publicar...
En el cuadro de diálogo Publicar:
Seleccione Microsoft Azure App Service.
Seleccione el icono de engranaje y luego Crear perfil.
Seleccione Crear perfil.
Crear recursos de Azure
Aparece el cuadro de diálogo Crear servicio de aplicaciones:
Especifique la suscripción.
Se rellenan los campos de entrada Nombre de la aplicación, Grupo de recursos y Plan de App
Service. Puede mantener estos nombres o cambiarlos.
Seleccione la pestaña Servicios para crear una base de datos.
Seleccione el icono verde + para crear una instancia de SQL Database.
Seleccione Nuevo... en el cuadro de diálogo Configurar SQL Database para crear una base de datos.
Se mostrará el cuadro de diálogo Configurar SQL Server.
Escriba un nombre de usuario y una contraseña de administrador y seleccione Aceptar. Puede conservar el
nombre de servidor predeterminado.
NOTE
No se permite "admin" como nombre de usuario de administrador.
Seleccione Aceptar.
Visual Studio volverá al cuadro de diálogo Crear un servicio de App Service.
Seleccione Crear en el cuadro de diálogo Crear un servicio de App Service.
Visual Studio crea la aplicación web y SQL Server en Azure. Este paso puede llevar varios minutos. Para más
información sobre los recursos creados, vea Recursos adicionales.
Cuando termine la implementación, seleccione Configuración:
Recursos adicionales
Azure App Service
Grupos de recursos de Azure
Azure SQL Database
Solución de problemas de ASP.NET Core en Azure App Service
Publicar una aplicación ASP.NET Core en Azure con
las herramientas de línea de comandos
14/05/2018 • 5 minutes to read • Edit Online
IMPORTANT
Aviso sobre el uso de las versiones preliminares de ASP.NET Core 2.1
Consulte Implementar una versión preliminar de ASP.NET Core en Azure App Service.
En este tutorial se explica cómo compilar e implementar una aplicación de ASP.NET Core en Microsoft Azure
App Service con las herramientas de línea de comandos. Cuando termine, tendrá una aplicación web integrada
en ASP.NET MVC Core hospedada como una aplicación web de Azure App Service. Este tutorial se escribe con
herramientas de línea de comandos de Windows, pero también puede aplicarse a entornos de macOS y Linux.
En este tutorial aprenderá a:
Crear un sitio web en Azure App Service con la CLI de Azure
Implementar una aplicación de ASP.NET Core en Azure App Service con la herramienta de línea de
comandos de Git
Requisitos previos
Para completar este tutorial, necesita:
Una suscripción de Microsoft Azure
.NET Core SDK 2.0 or later
Un cliente de línea de comandos de Git
Antes de la implementación, defina las credenciales de implementación a nivel de cuenta con el siguiente
comando:
az webapp deployment user set --user-name <desired user name> --password <desired password>
Se necesita una dirección URL de implementación para implementar la aplicación con Git. Recupere una
dirección URL como esta.
Anote la dirección URL mostrada que termina en .git . Se utiliza en el paso siguiente.
NOTE
Es seguro pasar por alto las advertencias de Git sobre los finales de línea.
Windows
Otros problemas
Git solicitará las credenciales de implementación establecidas anteriormente. Tras la autenticación, la aplicación
se inserta en la ubicación remota, se compila y se implementa.
Probar la aplicación
Navegue a https://<web app name>.azurewebsites.net para probar la aplicación. Para mostrar la dirección en
Cloud Shell o en la CLI de Azure, se usa lo siguiente:
Limpieza
Cuando termine de probar la aplicación y de inspeccionar el código y los recursos, elimine la aplicación web y el
plan con la eliminación del grupo de recursos.
IMPORTANT
Aviso sobre el uso de las versiones preliminares de ASP.NET Core 2.1
Consulte Implementar una versión preliminar de ASP.NET Core en Azure App Service.
En este tutorial se muestra cómo crear una aplicación web de ASP.NET Core con Visual Studio e implementarla
desde Visual Studio en Azure App Service mediante una implementación continua.
Vea también Use VSTS to Build and Publish to an Azure Web App with Continuous Deployment (Usar VSTS
para crear y publicar una aplicación web de Azure con la implementación continua), donde se muestra cómo
configurar un flujo de trabajo de una entrega continua (CD ) para Azure App Service con Visual Studio Team
Services. La entrega continua de Azure en Team Services simplifica la configuración de una canalización de
implementación sólida para publicar actualizaciones de aplicaciones hospedadas en Azure App Service. La
canalización se puede configurar desde Azure Portal para crear y ejecutar pruebas, implementarlas en un espacio
de ensayo y luego implementarlas en un entorno de producción.
NOTE
Para realizar este tutorial, necesita una cuenta de Microsoft Azure. Para obtener una, active las ventajas de suscriptor de
MSDN o regístrese para una prueba gratuita.
Requisitos previos
En este tutorial se da por hecho que está instalado el siguiente software:
Visual Studio
.NET Core SDK 2.0 or later
Git para Windows
NOTE
La versión más reciente de .NET Core es la 2.0.
2. Después de revisar la aplicación web en ejecución, cierre el explorador y seleccione el icono "Detener
depuración" de la barra de herramientas de Visual Studio para detener la aplicación.
También en la hoja Aplicación web, seleccione un plan o ubicación existente de App Service o bien cree
uno. Si va a crear un plan, seleccione el plan de tarifa, la ubicación y otras opciones. Para más información
sobre los planes de App Service, consulte Introducción detallada a los planes de Azure App Service.
5. Seleccione Crear. Azure aprovisionará e iniciará la aplicación web.
Habilitar la publicación de Git para la nueva aplicación web
Git es un sistema distribuido de control de versiones que se puede usar para implementar una aplicación web de
Azure App Service. El código de aplicación web se almacena en un repositorio de Git local y se implementa en
Azure mediante la inserción en un repositorio remoto.
1. Inicie sesión en Azure Portal.
2. Seleccione App Services para ver una lista los servicios de aplicaciones asociados a su suscripción de
Azure.
3. Seleccione la aplicación web que creó en la sección anterior de este tutorial.
4. En la hoja Implementación, seleccione Opciones de implementación > Elegir origen > Repositorio
de Git local.
5. Seleccione Aceptar.
6. Si no ha configurado antes las credenciales de implementación para publicar una aplicación web u otra
aplicación de App Service, configúrelas ahora:
Seleccione Configuración > Credenciales de implementación. Se muestra la hoja Configurar
credenciales de implementación.
Cree un nombre de usuario y una contraseña. Guarde la contraseña; la necesitará más adelante al
configurar Git.
Seleccione Guardar.
7. En la hoja Aplicación web, seleccione Configuración > Propiedades. La dirección URL del repositorio
de Git remoto en el que va a efectuar la implementación aparece en Dirección URL de Git.
8. Copie el valor de Dirección URL de Git para usarlo más adelante en el tutorial.
2. En Team Explorer, seleccione Inicio (icono Inicio) > Configuración > Configuración del repositorio.
3. En la sección Remotos de Configuración del repositorio, seleccione Agregar. Aparece el cuadro de
diálogo Agregar remoto.
4. Establezca el nombre del repositorio remoto en Azure-SampleApp.
5. Establezca el valor de Recuperar en la dirección URL de Git que copió de Azure anteriormente en este
tutorial. Tenga en cuenta que esta es la dirección URL que termina en .git.
NOTE
Como alternativa, especifique el repositorio remoto desde la ventana de comandos. Para ello, abra la ventana de
comandos, cambie al directorio del proyecto y escriba el comando. Ejemplo:
git remote add Azure-SampleApp https://[email protected]:443/SampleApp.git
6. Seleccione Inicio (icono de Inicio) > Configuración > Configuración global. Confirme que se
establecen el nombre y la direcciones de correo electrónico. Seleccione Actualizar si es necesario.
7. Seleccione Inicio > Cambios para volver a la vista Cambios.
8. Escriba un mensaje de confirmación, como Inserción inicial 1 y seleccione Confirmar. Esta acción crea
una confirmación localmente.
NOTE
Como alternativa, puede confirmar los cambios desde la ventana de comandos. Para ello, abra la ventana de
comandos, cambie al directorio del proyecto y escriba los comandos de Git. Ejemplo:
git add .
9. Seleccione Inicio > Sincronizar > Acciones > Abrir símbolo del sistema. El símbolo del sistema se
abre en el directorio del proyecto.
10. Escriba el siguiente comando en la ventana de comandos:
git push -u Azure-SampleApp master
11. Escriba la contraseña de sus credenciales de implementación de Azure que creó anteriormente en
Azure.
Este comando inicia el proceso de inserción de los archivos locales del proyecto en Azure. La salida del
comando anterior finaliza con un mensaje que indica que la implementación se efectuó correctamente.
NOTE
Como alternativa, inserte los cambios desde la ventana de comandos. Para ello, abra la ventana de comandos, cambie al
directorio del proyecto y escriba un comando de Git. Ejemplo:
git push -u Azure-SampleApp master
Recursos adicionales
Use VSTS to Build and Publish to an Azure Web App with Continuous Deployment (Uso de VSTS para
compilar y publicar una aplicación web de Azure con la implementación continua)
Proyecto Kudu
Solución de problemas de ASP.NET Core en Azure
App Service
25/06/2018 • 19 minutes to read • Edit Online
IMPORTANT
Aviso sobre el uso de las versiones preliminares de ASP.NET Core 2.1
Consulte Implementar una versión preliminar de ASP.NET Core en Azure App Service.
En este artículo se proporcionan instrucciones sobre cómo diagnosticar un problema de inicio de aplicaciones
ASP.NET Core mediante herramientas de diagnóstico de Azure App Service. Puede encontrar consejos
adicionales de solución de problemas en Introducción a los diagnósticos de Azure App Service y Supervisión de
Aplicaciones en Azure App Service en la documentación de Azure.
1. En la consola de diagnóstico de Kudu, vuelva a la ruta de acceso site > wwwroot para mostrar el archivo
web.config. Seleccione el icono de lápiz para abrir de nuevo el archivo web.config.
2. Establezca stdoutLogEnabled en false .
3. Seleccione Save (Guardar) para guardar el archivo.
WARNING
La imposibilidad de deshabilitar el registro de stdout puede dar lugar a un error de la aplicación o del servidor. No hay
ningún límite en el tamaño del archivo de registro ni en el número de archivos de registro creados. Use únicamente el
registro de stdout para solucionar problemas de inicio de la aplicación.
Para el registro general en una aplicación ASP.NET Core, use una biblioteca de registro que limite el tamaño del archivo de
registro y realice la rotación de los registros. Para más información, consulte los proveedores de registro de terceros.
Depuración remota
Consulte los temas siguientes:
Sección sobre la depuración remota de las aplicaciones web del artículo Solución de problemas de una
aplicación web en Azure App Service con Visual Studio (documentación de Azure)
Depuración remota de ASP.NET Core en IIS en Azure para Visual Studio 2017 (documentación de Visual
Studio)
Application Insights
Application Insights proporciona telemetría de las aplicaciones hospedadas en Azure App Service, lo que incluye
las características de registro de errores y generación de informes. Application Insights solo puede notificar los
errores que se producen después de que la aplicación se inicia cuando las características de registro de la
aplicación se vuelven disponibles. Para más información, consulte Application Insights para ASP.NET Core.
Hojas de supervisión
Las hojas de supervisión proporcionan una alternativa a la experiencia de solución de problemas de los métodos
descritos anteriormente en el tema. Estas hojas se pueden usar para diagnosticar errores de la serie 500.
Confirme que están instaladas las extensiones de ASP.NET Core. Si no lo están, instálelas manualmente:
1. En la sección de la hoja HERRAMIENTAS DE DESARROLLO, seleccione la hoja Extensiones.
2. Aparecerán en la lista las extensiones de ASP.NET Core.
3. Si las extensiones no están instaladas, seleccione el botón Add (Agregar).
4. Elija las extensiones de ASP.NET Core de la lista.
5. Seleccione Aceptar para aceptar los términos legales.
6. Seleccione Aceptar en la hoja Agregar extensión.
7. Un mensaje emergente informativo indica si las extensiones se han instalado correctamente.
Si el registro de stdout no está habilitado, siga estos pasos:
1. En Azure Portal, seleccione la hoja Herramientas avanzadas en el área HERRAMIENTAS DE
DESARROLLO. Seleccione el botón Ir→. Se abre la consola de Kudu en una nueva pestaña o ventana del
explorador.
2. Mediante la barra de navegación de la parte superior de la página, abra la consola de depuración y
seleccione CMD.
3. Abra las carpetas a la ruta de acceso sitio > wwwroot y desplácese hacia abajo para mostrar el archivo
web.config en la parte inferior de la lista.
4. Haga clic en el icono de lápiz junto al archivo web.config.
5. Establezca stdoutLogEnabled en true y cambie la ruta de acceso de stdoutLogFile a:
\\?\%home%\LogFiles\stdout .
6. Seleccione Save (Guardar) para guardar el archivo web.config actualizado.
Continúe para activar el registro de diagnóstico:
1. En Azure Portal, seleccione la hoja Registros de diagnóstico.
2. Seleccione el conmutador Activado en Registro de la aplicación (sistema de archivos) y Mensajes de
error detallados. Seleccione el botón Guardar en la parte superior de la hoja.
3. Para incluir el seguimiento de solicitudes con error, también conocido como almacenamiento en búfer de
eventos de solicitudes con error (FREB ), seleccione el conmutador Activado en Seguimiento de solicitudes
con error.
4. Seleccione la hoja Secuencia de registro, que aparece inmediatamente bajo la hoja Registros de
diagnóstico en el portal.
5. Realice una solicitud a la aplicación.
6. Dentro de los datos de la secuencia de registro, se indica la causa del error.
¡Importante! No olvide deshabilitar el registro de stdout cuando finalice la solución de problemas. Consulte las
instrucciones de la sección Registro de stdout del módulo ASP.NET Core.
Para ver los registros de seguimiento de solicitudes con error (registros FREB ):
1. Vaya a la hoja Diagnose and solve problems (Diagnosticar y resolver problemas) de Azure Portal.
2. Seleccione Failed Request Tracing Logs (Registros de seguimiento de solicitudes con error) en el área
SUPPORT TOOLS (HERRAMIENTAS DE SOPORTE TÉCNICO ) de la barra lateral.
Para más información, consulte la sección sobre los seguimientos de solicitudes con error del tema Habilitación
del registro de diagnóstico para aplicaciones web en Azure App Service y el artículo Preguntas más frecuentes
sobre el rendimiento de aplicaciones para Web Apps de Azure: ¿Cómo se activa el seguimiento de solicitudes con
error?.
Para más información, consulte Habilitación del registro de diagnóstico para aplicaciones web en Azure App
Service.
WARNING
La imposibilidad de deshabilitar el registro de stdout puede dar lugar a un error de la aplicación o del servidor. No hay
ningún límite en el tamaño del archivo de registro ni en el número de archivos de registro creados.
Para el registro rutinario en una aplicación ASP.NET Core, use una biblioteca de registro que limite el tamaño del archivo de
registro y realice la rotación de los registros. Para más información, consulte los proveedores de registro de terceros.
Recursos adicionales
Introducción a control de errores en ASP.NET Core
Referencia de errores comunes de Azure App Service e IIS con ASP.NET Core
Solución de problemas de una aplicación web en Azure App Service con Visual Studio
Solucionar los errores HTTP de "502 Puerta de enlace no válida" y "503 Servicio no disponible" en las
aplicaciones web de Azure
Solucionar los problemas de rendimiento reducido de aplicaciones web en Azure App Service
Preguntas más frecuentes sobre el rendimiento de aplicaciones para Web Apps de Azure
Azure Web App sandbox (App Service runtime execution limitations) (Espacio aislado de Azure Web App
[limitaciones de ejecución del entono de tiempo de ejecución de App Service])
Azure Friday: experiencia de diagnóstico y solución de problemas de Azure App Service (vídeo de 12 minutos)
Hospedaje de ASP.NET Core en Windows con IIS
28/05/2018 • 38 minutes to read • Edit Online
Configuración de aplicación
Habilitación de los componentes de integración con IIS
ASP.NET Core 2.x
ASP.NET Core 1.x
Los archivos Program.cs estándar llaman a CreateDefaultBuilder para empezar a configurar un host.
CreateDefaultBuilder configura Kestrel como el servidor web y habilita IIS Integration configurando la ruta de
acceso base y el puerto para el módulo ASP.NET Core:
El módulo ASP.NET Core genera un puerto dinámico que se asigna al proceso back-end. El método
UseIISIntegration toma el puerto dinámico y configura Kestrel para que escuche en
http://localhost:{dynamicPort}/ . Esto invalida otras configuraciones de URL, como las llamadas a UseUrls o a
la API Listen de Kestrel. Por lo tanto, no es necesario realizar llamadas a UseUrls o a la API Listen de Kestrel
cuando se usa el módulo. Si se llama a UseUrls o Listen , Kestrel escucha en el puerto especificado cuando se
ejecuta la aplicación sin IIS.
Para obtener más información sobre el hospedaje, consulte Hospedaje en ASP.NET Core.
Opciones de IIS
Para configurar las opciones de IIS, incluya una configuración del servicio para IISOptions en
ConfigureServices. En el ejemplo siguiente, el reenvío de los certificados de cliente a la aplicación para rellenar
HttpContext.Connection.ClientCertificate está deshabilitado:
services.Configure<IISOptions>(options =>
{
options.ForwardClientCertificate = false;
});
OPCIÓN DEFAULT PARÁMETRO
ForwardClientCertificate true Si
HttpContext.Connection.ClientCertificate
y el encabezado de solicitud true
está presente, se rellena
MS-ASPNETCORE-CLIENTCERT .
<Project Sdk="Microsoft.NET.Sdk.Web">
Si el proyecto no incluye un archivo web.config, el archivo se crea con los elementos processPath y arguments
correctos para configurar el módulo ASP.NET Core y se mueve a la salida publicada.
Si el proyecto incluye un archivo web.config, el archivo se transforma con los elementos processPath y
arguments correctos para configurar el módulo ASP.NET Core y se mueve a la salida publicada. La
transformación no modifica los valores de configuración de IIS del archivo.
El archivo web.config puede proporcionar valores de configuración de IIS adicionales que controlan los
módulos activos de IIS. Para información sobre los módulos de IIS que son capaces de procesar las solicitudes
con aplicaciones ASP.NET Core, vea el tema Módulos IIS.
Para evitar que el SDK web transforme el archivo web.config, use la propiedad
<IsTransformWebConfigDisabled> en el archivo del proyecto:
<PropertyGroup>
<IsTransformWebConfigDisabled>true</IsTransformWebConfigDisabled>
</PropertyGroup>
Al deshabilitar el SDK web para la transformación del archivo, el desarrollador debe establecer el elemento
processPath y los argumentos manualmente. Para más información, vea ASP.NET Core Module configuration
reference (Referencia de configuración del módulo de ASP.NET Core).
Ubicación del archivo web.config
Para crear el proxy inverso entre IIS y el servidor de Kestrel, el archivo web.config debe estar presente en la ruta
de acceso raíz del contenido (normalmente la ruta de acceso base de la aplicación) de la aplicación
implementada. Se trata de la misma ubicación que la ruta de acceso física del sitio web proporcionada a IIS. El
archivo web.config debe estar en la raíz de la aplicación para habilitar la publicación de varias aplicaciones
mediante Web Deploy.
Los archivos confidenciales están en la ruta de acceso física de la aplicación, como
<ensamblado>.runtimeconfig.json, <ensamblado>.xml (comentarios de documentación XML ) y
<ensamblado>.deps.json. Cuando el archivo web.config está presente y el sitio se inicia normalmente, IIS no
sirve estos archivos confidenciales si se solicitan. Si el archivo web.config no está presente, se le asignó un
nombre incorrecto o no se puede configurar el sitio para un inicio normal, IIS puede servir archivos
confidenciales públicamente.
El archivo web.config debe estar presente en la implementación en todo momento, se le debe asignar
un nombre correcto y debe ser capaz de configurar el sitio para el inicio normal. Nunca quite el
archivo web.config de una implementación de producción.
Configuración de IIS
Sistemas operativos de servidor Windows
Habilite el rol de servidor Servidor web (IIS ) y establezca los servicios de rol.
1. Use el asistente Agregar roles y características del menú Administrar o el vínculo de Administrador
del servidor. En el paso Roles de servidor, active la casilla de Servidor web (IIS ).
2. Después del paso Características, el paso Servicios de rol se carga para el servidor Web (IIS ).
Seleccione los servicios de rol IIS que quiera o acepte los servicios de rol predeterminados
proporcionados.
NOTE
Para obtener información sobre la configuración compartida de IIS, vea ASP.NET Core Module with IIS Shared
Configuration (Módulo de ASP.NET Core con configuración compartida de IIS).
3. En Administrador de IIS, abra el nodo del servidor en el panel Conexiones. Haga clic con el botón
derecho en la carpeta Sitios. Haga clic en Agregar sitio web en el menú contextual.
4. Proporcione el Nombre del sitio y establezca la Ruta de acceso física a la carpeta de implementación
de la aplicación. Proporcione la configuración de Enlace y cree el sitio web seleccionando Aceptar:
WARNING
Los enlaces de carácter comodín de nivel superior ( http://*:80/ y http://+:80 ) no se deben usar. Los
enlaces de carácter comodín de nivel superior pueden exponer su aplicación a vulnerabilidades de seguridad. Esto
se aplica tanto a los caracteres comodín fuertes como a los débiles. Use nombres de host explícitos en lugar de
caracteres comodín. Los enlaces de carácter comodín de subdominio (por ejemplo, *.mysub.com ) no suponen
este riesgo de seguridad si se controla todo el dominio primario (a diferencia de *.com , que sí es vulnerable). Vea
la sección 5.4 de RFC 7230 para obtener más información.
ASP.NET Core se ejecuta en un proceso independiente y administra el runtime. ASP.NET Core no se basa
en la carga de CLR de escritorio. El establecimiento de Versión de .NET CLR en Sin código
administrado es opcional.
8. Confirme que la identidad del modelo de proceso tiene los permisos adecuados.
Si cambia la identidad predeterminada del grupo de aplicaciones (Modelo de proceso > Identidad) de
ApplicationPoolIdentity a otra identidad, compruebe que la nueva identidad tenga los permisos
necesarios para obtener acceso a la carpeta de la aplicación, la base de datos y otros recursos necesarios.
Por ejemplo, el grupo de aplicaciones requiere acceso de lectura y escritura a las carpetas donde la
aplicación lee y escribe archivos.
Configuración de la autenticación de Windows (opcional)
Para más información, consulte Configurar la autenticación de Windows.
Implementación de la aplicación
Implemente la aplicación en la carpeta que ha creado en el sistema de hospedaje. Web Deploy es el mecanismo
recomendado para la implementación.
Web Deploy con Visual Studio
Vea el tema Visual Studio publish profiles for ASP.NET Core app deployment (Perfiles de publicación de Visual
Studio para la implementación de aplicaciones de ASP.NET Core) para obtener más información sobre cómo
crear un perfil de publicación para su uso con Web Deploy. Si el proveedor de hospedaje proporciona un perfil
de publicación o admite la creación de uno, descargue su perfil e impórtelo mediante el cuadro de diálogo
Publicar de Visual Studio.
Web Deploy fuera de Visual Studio
También puede usar Web Deploy fuera de Visual Studio desde la línea de comandos. Para más información, vea
Web Deployment Tool (Herramienta de implementación web).
Alternativas a Web Deploy
Use cualquiera de los métodos disponibles para mover la aplicación al sistema de hospedaje, como copia
manual, Xcopy, Robocopy o PowerShell.
Para obtener más información sobre ASP.NET Core en IIS, vea la sección Recursos de implementación para
administradores de IIS.
$webAppPoolName = 'APP_POOL_NAME'
Protección de datos
La pila de protección de datos de ASP.NET Core la usan varios middlewares de ASP.NET Core, incluidos los que
se emplean en la autenticación. Aunque el código de usuario no llame a las API de protección de datos, la
protección de datos se debe configurar con un script de implementación o en el código de usuario para crear un
almacén de claves criptográficas persistente. Si no se configura la protección de datos, las claves se conservan
en memoria y se descartan cuando se reinicia la aplicación.
Si el conjunto de claves se almacena en memoria cuando se reinicia la aplicación:
Todos los tokens de autenticación basados en cookies se invalidan.
Los usuarios tienen que iniciar sesión de nuevo en la siguiente solicitud.
Ya no se pueden descifrar los datos protegidos con el conjunto de claves. Esto puede incluir tokens CSRF y
cookies de TempData de ASP.NET Core MVC.
Para configurar la protección de datos en IIS para conservar el conjunto de claves, use uno de los enfoques
siguientes:
Crear claves del Registro de protección de datos
Las claves de protección de datos que las aplicaciones de ASP.NET usan se almacenan en el Registro
externo a las aplicaciones. Para conservar las claves de una determinada aplicación, cree claves del
Registro para el grupo de aplicaciones.
En las instalaciones independientes de IIS que no son de granja de servidores web, puede usar el script
de PowerShell Provision-AutoGenKeys.ps1 de protección de datos para cada grupo de aplicaciones
usado con una aplicación de ASP.NET Core. Este script crea una clave del Registro en el registro HKLM
que solo es accesible a la cuenta de proceso de trabajo del grupo de aplicaciones de la aplicación. Las
claves se cifran en reposo mediante DPAPI con una clave de equipo.
En escenarios de granja de servidores web, una aplicación puede configurarse para usar una ruta de
acceso UNC para almacenar su conjunto de claves de protección de datos. De forma predeterminada, las
claves de protección de datos no se cifran. Asegúrese de que los permisos de archivo de un recurso
compartido de red se limitan a la cuenta de Windows bajo la que se ejecuta la aplicación. Puede usar un
certificado X509 para proteger las claves en reposo. Considere un mecanismo que permita a los usuarios
cargar certificados: coloque los certificados en el almacén de certificados de confianza del usuario y
asegúrese de que están disponibles en todos los equipos en los que se ejecuta la aplicación del usuario.
Vea Configurar la protección de datos en ASP.NET Core para más información.
Configurar el grupo de aplicaciones de IIS para cargar el perfil de usuario
Esta opción está en la sección Modelo de proceso, en la Configuración avanzada del grupo de
aplicaciones. Establezca Cargar perfil de usuario en True . Esto almacena las claves en el directorio del
perfil de usuario y las protege mediante DPAPI con una clave específica de la cuenta de usuario que el
grupo de aplicaciones usa.
Usar el sistema de archivos como un almacén de conjunto de claves
Ajuste el código de la aplicación para usar el sistema de archivos como un almacén de conjunto de
claves. Use un certificado X509 para proteger el conjunto de claves y asegúrese de que sea un certificado
de confianza. Si es un certificado autofirmado, colóquelo en el almacén raíz de confianza.
Cuando se usa IIS en una granja de servidores web:
Use un recurso compartido de archivos al que puedan acceder todos los equipos.
Implemente un certificado X509 en cada equipo. Configure la protección de datos en el código.
Establecer una directiva para todo el equipo para la protección de datos
El sistema de protección de datos tiene compatibilidad limitada con el establecimiento de una directiva
de equipo predeterminada para todas las aplicaciones que usan las API de protección de datos. Vea la
documentación de protección de datos para más detalles.
Para más información sobre la configuración del módulo de ASP.NET Core, vea el tema Introducción al módulo
de ASP.NET Core y ASP.NET Core Module configuration reference (Referencia de configuración del módulo de
ASP.NET Core).
Grupos de aplicaciones
Al hospedar varios sitios web en un servidor, aísle las aplicaciones entre sí mediante la ejecución de cada una de
ellas en su propio grupo de aplicaciones. El valor predeterminado del cuadro de diálogo Agregar sitio web es
esta configuración. Cuando se proporciona el Nombre del sitio, el texto se transfiere automáticamente al
cuadro de texto Grupo de aplicaciones. Al agregar el sitio se crea un grupo de aplicaciones con el nombre del
sitio.
El proceso de administración de IIS crea un identificador seguro con el nombre del grupo de aplicaciones en el
sistema de seguridad de Windows. Los recursos se pueden proteger mediante esta identidad. Sin embargo, no
es una cuenta de usuario real ni se muestra en la consola de administración de usuario de Windows.
Si el proceso de trabajo de IIS requiere acceso con privilegios elevados a la aplicación, modifique la lista de
control de acceso (ACL ) del directorio que contiene la aplicación:
1. Abra el Explorador de Windows y vaya al directorio.
2. Haga clic con el botón derecho en el directorio y seleccione Propiedades.
3. En la pestaña Seguridad, haga clic en el botón Editar y en el botón Agregar.
4. Haga clic en el botón Ubicaciones y asegúrese de seleccionar el sistema.
5. Escriba IIS AppPool\<nombre_del_grupo_de_aplicaciones> en el área Escribir los nombres de
objeto para seleccionar. Haga clic en el botón Comprobar nombres. Para DefaultAppPool
compruebe los nombres con IIS AppPool\DefaultAppPool. Cuando el botón Comprobar nombres
está seleccionado, un valor de DefaultAppPool se indica en el área de los nombres de objeto. No es
posible escribir el nombre del grupo de aplicaciones directamente en el área de los nombres de objeto.
Use el formato IIS AppPool\<nombre_del_grupo_de_aplicaciones> cuando compruebe el nombre
del objeto.
6. Seleccione Aceptar.
7. Los permisos de lectura y ejecución se deben conceder de forma predeterminada. Proporcione permisos
adicionales según sea necesario.
El acceso también se puede conceder mediante un símbolo del sistema con la herramienta ICACLS. En el
siguiente comando se usa DefaultAppPool como ejemplo:
Recursos adicionales
Introducción a ASP.NET Core
Sitio oficial de Microsoft IIS
Biblioteca de contenido técnico de Windows Server
Solución de problemas de ASP.NET Core en IIS
25/06/2018 • 15 minutes to read • Edit Online
WARNING
La imposibilidad de deshabilitar el registro de stdout puede dar lugar a un error de la aplicación o del servidor. No hay
ningún límite en el tamaño del archivo de registro ni en el número de archivos de registro creados.
Para el registro rutinario en una aplicación ASP.NET Core, use una biblioteca de registro que limite el tamaño del archivo de
registro y realice la rotación de los registros. Para más información, consulte los proveedores de registro de terceros.
Solo se recomienda establecer la variable de entorno para ASPNETCORE_ENVIRONMENT cuando se use en servidores
de ensayo o pruebas que no estén expuestos a Internet. Quite la variable de entorno del archivo web.config
cuando termine de solucionar los problemas. Para información sobre la configuración de variables de entorno
en web.config, consulte el elemento secundario environmentVariables de aspNetCore.
Depuración remota
Consulte Depuración remota de ASP.NET Core en un equipo remoto de IIS en Visual Studio 2017 en la
documentación de Visual Studio.
Application Insights
Application Insights proporciona telemetría de las aplicaciones hospedadas en IIS, lo que incluye las
características de registro de errores y generación de informes. Application Insights solo puede notificar los
errores que se producen después de que la aplicación se inicia cuando las características de registro de la
aplicación se vuelven disponibles. Para más información, consulte Application Insights para ASP.NET Core.
TIP
Una forma práctica de borrar las memorias cachés del paquete es ejecutar dotnet nuget locals all --clear desde un
símbolo del sistema.
Otra manera de borrar las memorias caché del paquete es usar la herramienta nuget.exe y ejecutar el comando
nuget locals all -clear . nuget.exe no es una instalación agrupada con el sistema operativo de escritorio de Windows
y se debe obtener de forma independiente en el sitio web de NuGet.
Recursos adicionales
Introducción a control de errores en ASP.NET Core
Referencia de errores comunes de Azure App Service e IIS con ASP.NET Core
Referencia de configuración del módulo ASP.NET Core
Solución de problemas de ASP.NET Core en Azure App Service
Referencia de configuración del módulo ASP.NET
Core
25/06/2018 • 22 minutes to read • Edit Online
Cuando se implementa una aplicación en Azure App Service, la ruta de acceso de stdoutLogFile se establece
en \\?\%home%\LogFiles\stdout . La ruta de acceso guarda los registros de stdout en la carpeta LogFiles, que es
una ubicación que el servicio crea automáticamente.
Consulte Configuración de aplicaciones secundarias para ver una nota importante relativa a la configuración
de archivos web.config en aplicaciones secundarias.
Atributos del elemento aspNetCore
ATRIBUTO DESCRIPTION DEFAULT
WARNING
Establezca solo la variable de entorno ASPNETCORE_ENVIRONMENT en Development en servidores de ensayo y pruebas a
los que no puedan acceder redes que no son de confianza, como Internet.
app_offline.htm
Si un archivo con el nombre app_offline.htm se detecta en el directorio raíz de una aplicación, el módulo
ASP.NET Core intenta cerrar correctamente la aplicación y deja de procesar las solicitudes entrantes. Si la
aplicación se sigue ejecutando después del número definido en shutdownTimeLimit , el módulo ASP.NET Core
termina el proceso en ejecución.
Mientras el archivo app_offline.htm existe, el módulo ASP.NET Core responde a solicitudes con la devolución
del contenido del archivo app_offline.htm. Cuando se quita el archivo app_offline.htm, la solicitud siguiente
inicia la aplicación.
<aspNetCore processPath="dotnet"
arguments=".\MyApp.dll"
stdoutLogEnabled="true"
stdoutLogFile="\\?\%home%\LogFiles\stdout">
</aspNetCore>
Consulte Configuración con web.config para ver un ejemplo del elemento aspNetCore en el archivo web.config.
Requisitos previos
Visual Studio for Windows.
Select the ASP.NET and web development workload.
.Net Core 2.1 SDK
Habilitar IIS
1. Vaya a Panel de control > Programas > Programas y características > Activar o desactivar las
características de Windows (lado izquierdo de la pantalla).
2. Active la casilla Internet Information Services.
Configurar IIS
IIS debe tener un sitio web configurado con lo siguiente:
Un nombre de host que coincida con el nombre de host de la dirección URL del perfil de inicio de la aplicación.
Enlace al puerto 443 con un certificado asignado.
Por ejemplo, el nombre de host de un sitio web agregado se establece en "localhost" (el perfil de inicio también
usará "localhost" más adelante en este tema). El puerto se establece en "443" (HTTPS ). El certificado de
desarrollo de IIS Express se asigna al sitio web, pero cualquier certificado válido sirve:
Si la instalación de IIS ya tiene un sitio web predeterminado con un nombre de host que coincide con el nombre
de host de la dirección URL del perfil de inicio de la aplicación:
Agregue un enlace al puerto 443 (HTTPS ).
Asigne un certificado válido al sitio web.
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
}
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iis": {
"applicationUrl": "https://localhost/WebApplication1",
"sslPort": 0
}
},
"profiles": {
"IIS": {
"commandName": "IIS",
"launchBrowser": true,
"launchUrl": "https://localhost/WebApplication1",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Recursos adicionales
Hospedaje de ASP.NET Core en Windows con IIS
Introducción al módulo ASP.NET Core
Referencia de configuración del módulo ASP.NET Core
Aplicación de HTTPS
Módulos de IIS con ASP.NET Core
25/06/2018 • 10 minutes to read • Edit Online
Módulos nativos
En la tabla se indican los módulos nativos de IIS que son funcionales con solicitudes de proxy inverso en
aplicaciones ASP.NET Core.
Autenticación anónima Sí
AnonymousAuthenticationModule
Autenticación básica Sí
BasicAuthenticationModule
Autenticación de asignaciones de Sí
certificados de cliente
CertificateMappingAuthenticationModule
CGI No
CgiModule
Validación de configuración Sí
ConfigurationValidationModule
Registro personalizado Sí
CustomLoggingModule
Autenticación implícita Sí
DigestAuthenticationModule
Autenticación de asignaciones de Sí
certificados de cliente IIS
IISCertificateMappingAuthenticationModule
Restricciones de IP y dominio Sí
IpRestrictionModule
ISAPI Sí Middleware
IsapiModule
Supervisor de solicitudes Sí
RequestMonitorModule
Almacenamiento en caché de Sí
tokens.
TokenCacheModule
Autenticación de Windows Sí
WindowsAuthenticationModule
†Los tipos de coincidencia isFile y isDirectory del módulo de reescritura de direcciones URL no funcionan
con las aplicaciones ASP.NET Core debido a los cambios en la estructura de directorios.
Módulos administrados
Los módulos administrados no son funcionales cuando la versión de CLR de .NET del grupo de aplicaciones se
establece en No Managed Code (Sin código administrado). ASP.NET Core ofrece alternativas de middleware en
varios casos.
AnonymousIdentification
DefaultAuthentication
FileAuthorization
Perfil
RoleManager
ScriptModule 4.0
UrlAuthorization
WindowsAuthentication
Cambios en la aplicación del Administrador de IIS
Al usar el Administrador de IIS para realizar la configuración, cambia el archivo web.config de la aplicación. Si
implementa una aplicación e incluye web.config, los cambios realizados con el Administrador de IIS se
sobrescriben con el archivo web.config implementado. Si se realizan cambios en el archivo web.config del
servidor, copie el archivo web.config actualizado en el servidor en el proyecto local inmediatamente.
<configuration>
<system.webServer>
<httpRedirect enabled="false" />
</system.webServer>
</configuration>
Para más información sobre la deshabilitación de los módulos con valores de configuración, siga los vínculos de
la sección sobre elementos secundarios de IIS <system.webServer>.
Eliminación de módulos
Si opta por quitar un módulo con un valor de configuración en web.config, primero desbloquee el módulo y
desbloquee la sección <modules> de web.config:
1. Desbloquee el módulo en el nivel de servidor. Seleccione el servidor de IIS en la barra lateral Conexiones
del Administrador de IIS. Abra Módulos en el área IIS. Seleccione el módulo de la lista. En la barra lateral
Acciones e la derecha, seleccione Desbloquear. Desbloquee tantos módulos como quiera quitar de
web.config más tarde.
2. Implemente la aplicación sin una sección <modules> en web.config. Si una aplicación se implementa con
un archivo web.config que contiene la sección <modules> sin haber desbloqueado primero la sección en
el Administrador de IIS, Configuration Manager produce una excepción al intentar desbloquear la sección.
Por lo tanto, implementar la aplicación sin una sección <modules>.
3. Desbloquee la sección <modules> de web.config. En la barra lateral Conexiones, seleccione el sitio web
en Sitios. En el área Administración, abra el error de configuración. Use los controles de navegación
para seleccionar la sección system.webServer/modules . En la barra lateral Acciones de la derecha,
seleccione Desbloquear la sección.
4. En este punto, se puede agregar una sección <modules> al archivo web.config con un elemento
<remove> para quitar el módulo de la aplicación. Se pueden agregar varios elementos <remove> para
quitar varios módulos. Si se realizan cambios en web.config en el servidor, realice inmediatamente los
mismos cambios en el archivo web.config el proyecto de forma local. Quitar un módulo de esta manera no
afecta al uso del módulo con otras aplicaciones del servidor.
<configuration>
<system.webServer>
<modules>
<remove name="MODULE_NAME" />
</modules>
</system.webServer>
</configuration>
Un módulo de IIS también se puede quitar con Appcmd.exe. Proporcione MODULE_NAME y APPLICATION_NAME en el
comando:
Recursos adicionales
Hospedaje en Windows con IIS
Introduction to IIS Architectures: Modules in IIS (Introducción a las arquitecturas de IIS: módulos de IIS )
IIS Modules Overview (Introducción a los módulos de IIS )
Customizing IIS 7.0 Roles and Modules (Personalización de los roles y módulos de IIS 7.0)
IIS <system.webServer>
Hospedaje de ASP.NET Core en un servicio de
Windows
31/05/2018 • 7 minutes to read • Edit Online
Requisitos previos
La aplicación debe ejecutarse en el entorno de tiempo de ejecución de .NET Framework. En el archivo
.csproj, especifique los valores adecuados para TargetFramework y RuntimeIdentifier. Por ejemplo:
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeIdentifier>win7-x64</RuntimeIdentifier>
</PropertyGroup>
Al crear un proyecto en Visual Studio, use la plantilla Aplicación ASP.NET Core (.NET Framework).
Si la aplicación recibe las solicitudes de Internet (no solo desde una red interna), debe usar el servidor web
HTTP.sys (antes conocidos como WebListener para las aplicaciones ASP.NET Core 1.x) en lugar de Kestrel.
Se recomienda el uso de IIS como un servidor proxy inverso con Kestrel en implementaciones perimetrales.
Para más información, vea When to use Kestrel with a reverse proxy (Cuándo se debe usar Kestrel con un
proxy inverso).
Primeros pasos
En esta sección se explican los cambios mínimos necesarios para configurar un proyecto de ASP.NET Core
existente para que se ejecute en un servicio.
1. Instale el paquete NuGet Microsoft.AspNetCore.Hosting.WindowsServices.
2. Realice los siguientes cambios en Program.Main .
Llame a host.RunAsService en lugar de a host.Run .
Si el código llama a UseContentRoot , use una ruta de acceso a la ubicación de publicación en lugar de
Directory.GetCurrentDirectory() .
ASP.NET Core 2.x
ASP.NET Core 1.x
public static void Main(string[] args)
{
var pathToExe = Process.GetCurrentProcess().MainModule.FileName;
var pathToContentRoot = Path.GetDirectoryName(pathToExe);
host.RunAsService();
}
3. Publique la aplicación en una carpeta. Use dotnet publish o un perfil de publicación de Visual Studio que
publique en una carpeta.
4. Para probar esto, cree e inicie el servicio.
Abra un shell de comandos con privilegios administrativos para usar la herramienta de la línea de
comandos sc.exe para crear e iniciar un servicio. Si el servicio se llamó MyService, se publicó en c:\svc y
se llamó AspNetCoreService, los comandos son:
El valor binPath es la ruta de acceso al archivo ejecutable de la aplicación, que incluye el nombre del
archivo ejecutable.
Cuando finalicen estos comandos, vaya a la misma ruta de acceso que cuando se ejecutó como una
aplicación de consola (de forma predeterminada, http://localhost:5000 ):
if (Debugger.IsAttached || args.Contains("--console"))
{
isService = false;
}
if (isService)
{
var pathToExe = Process.GetCurrentProcess().MainModule.FileName;
pathToContentRoot = Path.GetDirectoryName(pathToExe);
}
if (isService)
{
host.RunAsService();
}
else
{
host.Run();
}
}
2. Crear un método de extensión para IWebHost que pase el elemento WebHostService personalizado a
ServiceBase.Run :
if (Debugger.IsAttached || args.Contains("--console"))
{
isService = false;
}
if (isService)
{
var pathToExe = Process.GetCurrentProcess().MainModule.FileName;
pathToContentRoot = Path.GetDirectoryName(pathToExe);
}
if (isService)
{
host.RunAsCustomService();
}
else
{
host.Run();
}
}
Agradecimientos
El artículo se escribió con ayuda de fuentes publicadas:
Hosting ASP.NET Core as Windows service (Hospedaje de ASP.NET Core como servicio de Windows)
How to host your ASP.NET Core in a Windows Service (Cómo hospedar ASP.NET Core en un servicio de
Windows)
Hospedar ASP.NET Core en Linux con Nginx
31/05/2018 • 18 minutes to read • Edit Online
NOTE
En Ubuntu 14.04, supervisord is es la solución recomendada para supervisar el proceso de Kestrel. systemd no está
disponible en Ubuntu 14.04. Consulte la versión anterior de este documento.
En esta guía:
Se coloca una aplicación ASP.NET Core existente detrás de un servidor proxy inverso.
Se configura el servidor proxy inverso para reenviar las solicitudes al servidor web de Kestrel.
Se garantiza que la aplicación web se ejecute al inicio como un demonio.
Se configura una herramienta de administración de procesos para ayudar a reiniciar la aplicación web.
Requisitos previos
1. Acceso a un servidor de Ubuntu 16.04 con una cuenta de usuario estándar con privilegios sudo
2. Una aplicación ASP.NET Core existente
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseAuthentication();
NOTE
Si se van a instalar módulos de Nginx opcionales, podría ser necesario compilar Nginx desde el origen.
Use apt-get para instalar Nginx. El instalador crea un script de inicio de System V que ejecuta Nginx como
demonio al iniciarse el sistema. Puesto que Nginx se ha instalado por primera vez, ejecute lo siguiente para
iniciarlo de forma explícita:
Cuando no hay ninguna coincidencia de server_name , Nginx usa el servidor predeterminado. Si no se define
ningún servidor predeterminado, el primer servidor del archivo de configuración es el servidor predeterminado.
Como procedimiento recomendado, agregue un servidor predeterminado específico que devuelva un código de
estado 444 en el archivo de configuración. Un ejemplo de configuración del servidor predeterminado es:
server {
listen 80 default_server;
# listen [::]:80 default_server deferred;
return 444;
}
Con el archivo de configuración anterior y el servidor predeterminado, Nginx acepta tráfico público en el puerto
80 con el encabezado de host example.com o *.example.com . Las solicitudes que no coincidan con estos hosts
no se reenviarán al Kestrel. Nginx reenvía las solicitudes coincidentes con Kestrel a http://localhost:5000 . Para
más información, consulte How nginx processes a request (Cómo Nginx procesa una solicitud).
WARNING
Si no se especifica una directiva de server_name adecuada, su aplicación se expone a vulnerabilidades de seguridad. Los
enlaces de carácter comodín de subdominio (por ejemplo, *.example.com ) no presentan este riesgo de seguridad si se
controla todo el dominio primario (a diferencia de *.com , que sí es vulnerable). Vea la sección 5.4 de RFC 7230 para
obtener más información.
Una vez establecida la configuración de Nginx, ejecute sudo nginx -t para comprobar la sintaxis de los archivos
de configuración. Si la prueba del archivo de configuración es correcta, fuerce a Nginx a recopilar los cambios
mediante la ejecución de sudo nginx -s reload .
Supervisión de la aplicación
Nginx ahora está configurado para reenviar las solicitudes realizadas a http://<serveraddress>:80 en la
aplicación de ASP.NET Core que se ejecuta en Kestrel en http://127.0.0.1:5000 . Sin embargo, Nginx no está
configurado para administrar el proceso de Kestrel. Se puede usar systemd para crear un archivo de servicio
para iniciar y supervisar la aplicación web subyacente. systemd es un sistema de inicio que proporciona muchas
características eficaces para iniciar, detener y administrar procesos.
Crear el archivo de servicio
Cree el archivo de definición de servicio:
[Service]
WorkingDirectory=/var/aspnetcore/hellomvc
ExecStart=/usr/bin/dotnet /var/aspnetcore/hellomvc/hellomvc.dll
Restart=always
RestartSec=10 # Restart service after 10 seconds if dotnet service crashes
SyslogIdentifier=dotnet-example
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
[Install]
WantedBy=multi-user.target
Si el usuario www -data no se usó en la configuración, primero se debe crear el usuario aquí definido y
otorgársele la propiedad adecuada de los archivos.
Linux tiene un sistema de archivos que distingue mayúsculas de minúsculas. Al establecer
ASPNETCORE_ENVIRONMENT en "Production", se busca el archivo de configuración
appsettings.Production.json, en lugar de appsettings.production.json.
NOTE
Algunos valores (por ejemplo, cadenas de conexión de SQL) deben ser de escape para que los proveedores de
configuración lean las variables de entorno. Use el siguiente comando para generar un valor de escape correctamente para
su uso en el archivo de configuración:
systemd-escape "<value-to-escape>"
Con el proxy inverso configurado y Kestrel administrado a través de systemd, la aplicación web está
completamente configurada y se puede acceder a ella desde un explorador en la máquina local en
http://localhost . También es accesible desde una máquina remota, salvo que haya algún firewall que la pueda
estar bloqueando. Al inspeccionar los encabezados de respuesta, el encabezado Server muestra que Kestrel
suministra la aplicación ASP.NET Core.
HTTP/1.1 200 OK
Date: Tue, 11 Oct 2016 16:22:23 GMT
Server: Kestrel
Keep-Alive: timeout=5, max=98
Connection: Keep-Alive
Transfer-Encoding: chunked
Para obtener más opciones de filtrado, las opciones de tiempo como --since today , --until 1 hour ago o una
combinación de estas pueden reducir la cantidad de entradas que se devuelven.
Protección de la aplicación
Habilitar AppArmor
Linux Security Modules (LSM ) es una plataforma que forma parte del kernel de Linux desde Linux 2.6. LSM
admite diferentes implementaciones de los módulos de seguridad. AppArmor es un LSM que implementa un
sistema de control de acceso obligatorio que permite restringir el programa a un conjunto limitado de recursos.
Asegúrese de que AppArmor está habilitado y configurado correctamente.
Configuración del firewall
Cierre todos los puertos externos que no estén en uso. Uncomplicated Firewall (ufw ) proporciona un front-end
para iptables al proporcionar una interfaz de línea de comandos para configurar el firewall. Compruebe que
ufw está configurado para permitir el tráfico en los puertos necesarios.
Proteger Nginx
La distribución predeterminada de Nginx no habilita SSL. Para habilitar características de seguridad adicionales,
compile desde el origen.
Descargar el origen e instalar las dependencias de compilación
# Install the build dependencies
sudo apt-get update
sudo apt-get install build-essential zlib1g-dev libpcre3-dev libssl-dev libxslt1-dev libxml2-dev libgd2-xpm-
dev libgeoip-dev libgoogle-perftools-dev libperl-dev
./configure
--with-pcre=../pcre-8.38
--with-zlib=../zlib-1.2.8
--with-http_ssl_module
--with-stream
--with-mail=dynamic
Configurar SSL
Configure el servidor para que escuche el tráfico HTTPS en el puerto 443 . Para ello, especifique un
certificado válido emitido por una entidad de certificados (CA) de confianza.
Refuerce la seguridad con algunos de los procedimientos descritos en el siguiente archivo
/etc/nginx/nginx.conf. Entre los ejemplos se incluye la elección de un cifrado más seguro y el
redireccionamiento de todo el tráfico a través de HTTP a HTTPS.
Agregar un encabezado HTTP Strict-Transport-Security (HSTS ) garantiza que todas las solicitudes
siguientes realizadas por el cliente sean solo a través de HTTPS.
No agregue el encabezado Strict-Transport-Security o elija un valor de max-age adecuado si tiene
previsto deshabilitar SSL en el futuro.
Agregue el archivo de configuración /etc/nginx/proxy.conf:
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 10m;
client_body_buffer_size 128k;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffers 32 4k;
Edite el archivo de configuración /etc/nginx/nginx.conf. El ejemplo contiene las dos secciones http y server en
un archivo de configuración.
http {
include /etc/nginx/proxy.conf;
limit_req_zone $binary_remote_addr zone=one:10m rate=5r/s;
server_tokens off;
sendfile on;
keepalive_timeout 29; # Adjust to the lowest possible value that makes sense for your use case.
client_body_timeout 10; client_header_timeout 10; send_timeout 10;
upstream hellomvc{
server localhost:5000;
}
server {
listen *:80;
add_header Strict-Transport-Security max-age=15768000;
return 301 https://$host$request_uri;
}
server {
listen *:443 ssl;
server_name example.com;
ssl_certificate /etc/ssl/certs/testCert.crt;
ssl_certificate_key /etc/ssl/certs/testCert.key;
ssl_protocols TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
ssl_ecdh_curve secp384r1;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling on; #ensure your cert is capable
ssl_stapling_verify on; #ensure your cert is capable
Agregue la línea add_header X-Frame-Options "SAMEORIGIN"; y guarde el archivo, después, reinicie Nginx.
Examen de tipo MIME
Este encabezado evita que la mayoría de los exploradores examinen el MIME de una respuesta fuera del tipo de
contenido declarado, ya que el encabezado indica al explorador que no reemplace el tipo de contenido de
respuesta. Con la opción nosniff , si el servidor indica que el contenido es "text/html", el explorador lo
representa como "text/html".
Edite el archivo nginx.conf:
Agregue la línea add_header X-Content-Type-Options "nosniff"; , guarde el archivo y después reinicie Nginx.
Hospedar ASP.NET Core en Linux con Apache
31/05/2018 • 17 minutes to read • Edit Online
Requisitos previos
1. Servidor que ejecute CentOS 7, con una cuenta de usuario estándar con privilegios sudo
2. Aplicación ASP.NET Core
Publicar la aplicación
Publique la aplicación como una implementación independiente en la configuración de versión del tiempo de
ejecución de CentOS 7 ( centos.7-x64 ). Copie el contenido de la carpeta bin/Release/netcoreapp2.0/centos.7 -
x64/publish en el servidor mediante SCP, FTP u otro método de transferencia de archivos.
NOTE
En un escenario de implementación de producción, un flujo de trabajo de integración continua lleva a cabo la tarea de
publicar la aplicación y copiar los recursos en el servidor.
app.UseAuthentication();
Downloading packages:
httpd-2.4.6-40.el7.centos.4.x86_64.rpm | 2.7 MB 00:00:01
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
Installing : httpd-2.4.6-40.el7.centos.4.x86_64 1/1
Verifying : httpd-2.4.6-40.el7.centos.4.x86_64 1/1
Installed:
httpd.x86_64 0:2.4.6-40.el7.centos.4
Complete!
NOTE
En este ejemplo, la salida refleja httpd.86_64, puesto que la versión de CentOS 7 es de 64 bits. Para comprobar dónde
está instalado Apache, ejecute whereis httpd desde un símbolo del sistema.
El bloque VirtualHost puede aparecer varias veces en uno o varios archivos en un servidor. En el archivo de
configuración anterior, Apache acepta tráfico público en el puerto 80. El dominio www.example.com se atiende y
el alias *.example.com se resuelve en el mismo sitio web. Para más información, consulte Name-based virtual
host support (Compatibilidad con el host virtual basado en nombres). Las solicitudes se redirigen mediante
proxy en la raíz al puerto 5000 del servidor en 127.0.0.1. Para la comunicación bidireccional, se requieren
ProxyPass y ProxyPassReverse .
WARNING
Si no se especifica una directiva de ServerName correcta en VirtualHost, el bloque expone la aplicación a las
vulnerabilidades de seguridad. Los enlaces de carácter comodín de subdominio (por ejemplo, *.example.com ) no
presentan este riesgo de seguridad si se controla todo el dominio primario (a diferencia de *.com , que sí es vulnerable).
Vea la sección 5.4 de RFC 7230 para obtener más información.
El registro se puede configurar por VirtualHost con las directivas ErrorLog y CustomLog . ErrorLog es la
ubicación donde el servidor registra los errores, y CustomLog establece el nombre de archivo y el formato del
archivo de registro. En este caso, aquí es donde se registra la información de la solicitud. Hay una línea para
cada solicitud.
Guarde el archivo y pruebe la configuración. Si se pasa todo, la respuesta debe ser Syntax [OK] .
Reinicie Apache:
Supervisión de la aplicación
Apache está configurado ahora para reenviar las solicitudes efectuadas a http://localhost:80 en la aplicación
ASP.NET Core que se ejecuta en Kestrel en http://127.0.0.1:5000 . En cambio, Apache no está configurado para
administrar el proceso de Kestrel. Use systemd y cree un archivo de servicio para iniciar y supervisar la
aplicación web subyacente. systemd es un sistema de inicio que proporciona muchas características eficaces
para iniciar, detener y administrar procesos.
Crear el archivo de servicio
Cree el archivo de definición de servicio:
[Service]
WorkingDirectory=/var/aspnetcore/hellomvc
ExecStart=/usr/local/bin/dotnet /var/aspnetcore/hellomvc/hellomvc.dll
Restart=always
# Restart service after 10 seconds if dotnet service crashes
RestartSec=10
SyslogIdentifier=dotnet-example
User=apache
Environment=ASPNETCORE_ENVIRONMENT=Production
[Install]
WantedBy=multi-user.target
NOTE
Usuario: si el usuario apache no se usa en la configuración, primero se debe crear el usuario y se le debe conceder la
propiedad adecuada para los archivos.
NOTE
Algunos valores (por ejemplo, cadenas de conexión de SQL) deben ser de escape para que los proveedores de
configuración lean las variables de entorno. Use el siguiente comando para generar un valor de escape correctamente para
su uso en el archivo de configuración:
systemd-escape "<value-to-escape>"
Con el proxy inverso configurado y Kestrel administrado mediante systemd, la aplicación web está
completamente configurada y se puede acceder a ella desde un explorador en la máquina local en
http://localhost . Inspeccionar los encabezados de respuesta; el encabezado Server indica que Kestrel atiende
la aplicación ASP.NET Core:
HTTP/1.1 200 OK
Date: Tue, 11 Oct 2016 16:22:23 GMT
Server: Kestrel
Keep-Alive: timeout=5, max=98
Connection: Keep-Alive
Transfer-Encoding: chunked
Para el filtrado de tiempo, especifique las opciones de tiempo con el comando. Por ejemplo, use --since today
para filtrar por el día actual o --until 1 hour ago para ver las entradas de la hora anterior. Para más
información, consulte la página man de journalctl.
Protección de la aplicación
Configurar el firewall
Firewalld es un demonio dinámico para administrar el firewall con compatibilidad con zonas de red. Los puertos
y el filtrado de paquetes se pueden seguir administrando mediante iptables. Firewalld debe instalarse de forma
predeterminada. yum puede usarse para instalar el paquete o comprobar que está instalado.
Use firewalld para abrir solo los puertos necesarios para la aplicación. En este caso se usan los puertos 80 y
443. Los siguientes comandos establecen de forma permanente que se abran los puertos 80 y 443:
Vuelva a cargar la configuración del firewall. Compruebe los servicios y puertos disponibles en la zona
predeterminada. Hay opciones disponibles si se inspecciona firewall-cmd -h .
Configuración de SSL
Para configurar Apache para SSL, se usa el módulo mod_ssl. Cuando se instaló el módulo httpd, también lo hizo
el módulo mod_ssl. Si aún no se ha instalado, use yum para agregarlo a la configuración.
Para usar SSL, instale el módulo mod_rewrite para habilitar la reescritura de direcciones URL:
Modifique el archivo hellomvc.conf para permitir la reescritura de direcciones URL y proteger la comunicación
en el puerto 443:
<VirtualHost *:80>
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^/?(.*) https://%{SERVER_NAME}/ [R,L]
</VirtualHost>
<VirtualHost *:443>
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:5000/
ProxyPassReverse / http://127.0.0.1:5000/
ErrorLog /var/log/httpd/hellomvc-error.log
CustomLog /var/log/httpd/hellomvc-access.log common
SSLEngine on
SSLProtocol all -SSLv2
SSLCipherSuite ALL:!ADH:!EXPORT:!SSLv2:!RC4+RSA:+HIGH:+MEDIUM:!LOW:!RC4
SSLCertificateFile /etc/pki/tls/certs/localhost.crt
SSLCertificateKeyFile /etc/pki/tls/private/localhost.key
</VirtualHost>
NOTE
En este ejemplo se usa un certificado generado localmente. SSLCertificateFile debe ser el archivo de certificado principal
para el nombre de dominio. SSLCertificateKeyFile debe ser el archivo de claves generado al crear el CSR.
SSLCertificateChainFile debe ser el archivo de certificado intermedio (si existe) proporcionado por la entidad de
certificación.
Reinicie Apache:
sudo systemctl restart httpd
Agregue la línea Header append X-FRAME-OPTIONS "SAMEORIGIN" . Guarde el archivo. Reinicie Apache.
Examen de tipo MIME
El encabezado impide que Internet Explorer examine MIME (de forma que el elemento
X-Content-Type-Options
Content-Type de un archivo se determina a partir del contenido del archivo). Si el servidor establece el
encabezado Content-Type en text/html con la opción nosniff establecida, Internet Explorer representa el
contenido como text/html sin tener en cuenta el contenido del archivo.
Edite el archivo httpd.conf.
Agregue la línea Header set X-Content-Type-Options "nosniff" . Guarde el archivo. Reinicie Apache.
Equilibrio de carga
En este ejemplo se muestra cómo instalar y configurar Apache en CentOS 7 y Kestrel en el mismo equipo de la
instancia. Para no tener un único punto de error, el uso de mod_proxy_balancer y la modificación de
VirtualHost permitirían administrar varias instancias de las aplicaciones web detrás del servidor proxy de
Apache.
<VirtualHost *:443>
ProxyPass / balancer://mycluster/
ProxyPassReverse / http://127.0.0.1:5000/
ProxyPassReverse / http://127.0.0.1:5001/
<Proxy balancer://mycluster>
BalancerMember http://127.0.0.1:5000
BalancerMember http://127.0.0.1:5001
ProxySet lbmethod=byrequests
</Proxy>
<Location />
SetHandler balancer
</Location>
ErrorLog /var/log/httpd/hellomvc-error.log
CustomLog /var/log/httpd/hellomvc-access.log common
SSLEngine on
SSLProtocol all -SSLv2
SSLCipherSuite ALL:!ADH:!EXPORT:!SSLv2:!RC4+RSA:+HIGH:+MEDIUM:!LOW:!RC4
SSLCertificateFile /etc/pki/tls/certs/localhost.crt
SSLCertificateKeyFile /etc/pki/tls/private/localhost.key
</VirtualHost>
Límites de velocidad
Use mod_ratelimit, que se incluye en el módulo httpd; el ancho de banda de los clientes puede ser limitado:
<IfModule mod_ratelimit.c>
<Location />
SetOutputFilter RATE_LIMIT
SetEnv rate-limit 600
</Location>
</IfModule>
Hospedar ASP.NET Core en contenedores de Docker
21/06/2018 • 2 minutes to read • Edit Online
Tiene a su disposición los siguientes artículos para aprender a hospedar aplicaciones de ASP.NET Core en Docker:
Introducción a los contenedores y Docker
Obtenga información sobre la inclusión en contenedores, un enfoque de desarrollo de software en el que una
aplicación o un servicio, sus dependencias y su configuración se empaquetan como una imagen de contenedor.
Puede probar la imagen y, después, implementarla en un host.
¿Qué es Docker?
Descubra Docker, un proyecto de código abierto para automatizar la implementación de aplicaciones como
contenedores portátiles y autosuficientes que se pueden ejecutar en la nube o localmente.
Terminología de Docker
Conozca los términos y las definiciones de la tecnología de Docker.
Contenedores, imágenes y registros de Docker
Descubra cómo se almacenan las imágenes de contenedor de Docker en un registro de imágenes para la
implementación coherente en los entornos.
Creación de imágenes de Docker para aplicaciones de .NET Core
Obtenga información sobre cómo compilar una aplicación de ASP.NET Core y aplicarle Docker. Explore las
imágenes de Docker que mantiene Microsoft y examine los casos de uso.
Visual Studio Tools para Docker
Descubra la manera en que Visual Studio 2017 admite la compilación, la depuración y la ejecución de aplicaciones
de ASP.NET Core destinadas a .NET Framework o .NET Core en Docker para Windows. Se admiten contenedores
de Windows y Linux.
Publicación en una imagen de Docker
Obtenga información sobre cómo usar la extensión de Visual Studio Tools para Docker para implementar una
aplicación de ASP.NET Core en un host de Docker en Azure mediante PowerShell.
Configurar ASP.NET Core para trabajar con servidores proxy y equilibradores de carga
Podría ser necesario realizar una configuración adicional para las aplicaciones hospedadas detrás de servidores
proxy y equilibradores de carga. El proceso de pasar solicitudes a través de un proxy suele ocultar información de
la solicitud original, como la dirección IP de cliente y el esquema. Podría ser necesario reenviar manualmente a la
aplicación cierta información de la solicitud.
Visual Studio Tools para Docker con ASP.NET Core
25/06/2018 • 12 minutes to read • Edit Online
Visual Studio 2017 permite compilar, depurar y ejecutar aplicaciones ASP.NET Core en contenedor destinadas a
.NET Core. Se admiten contenedores de Windows y Linux.
Requisitos previos
Visual Studio 2017 con la carga de trabajo Desarrollo multiplataforma de .NET Core
Docker para Windows
Instalación y configuración
Para la instalación de Docker, revise la información de Docker for Windows: What to know before you install
(Docker para Windows: Información antes de realizar la instalación) e instale Docker para Windows.
Las unidades compartidas de Docker para Windows deben configurarse para admitir la asignación y la
depuración de volúmenes. Haga clic con el botón derecho en el icono de Docker en la bandeja del sistema, haga
clic en Configuración... y seleccione Unidades compartidas. Seleccione la unidad donde los archivos se
almacenan en Docker. Seleccione Aplicar.
TIP
Las versiones 15.6 y posteriores de Visual Studio 2017 le avisan si las unidades compartidas no están configuradas.
Si la plataforma de destino es .NET Core, la lista desplegable de SO permite la selección de un tipo de contenedor.
Aplicación existente
Visual Studio Tools para Docker no admite la adición de Docker a un proyecto de ASP.NET Core existente para
.NET Framework. En los proyectos de ASP.NET Core para .NET Core, hay dos opciones para agregar
compatibilidad con Docker mediante las herramientas. Abra el proyecto en Visual Studio y elija una de las
siguientes opciones:
Seleccione Compatibilidad con Docker en el menú Proyecto.
Haga clic con el botón derecho en el proyecto en el Explorador de soluciones y seleccione Agregar >
Compatibilidad con Docker.
Se agrega un Dockerfile, la receta para crear una imagen de Docker final, a la raíz del proyecto. Vea Dockerfile
reference (Referencia de Dockerfile) para obtener una descripción de los comandos que contiene. Este Dockerfile
concreto usa una compilación de varias fases con cuatro fases de compilación distintas con nombre:
FROM microsoft/aspnetcore:2.0-nanoserver-1709 AS base
WORKDIR /app
EXPOSE 80
El Dockerfile se basa en la imagen microsoft/aspnetcore. Esta imagen base incluye los paquetes NuGet de
ASP.NET Core, que se han precompilado para mejorar el rendimiento en el inicio.
El archivo docker-compose.yml contiene el nombre de la imagen que se crea al ejecutar el proyecto:
version: '3'
services:
hellodockertools:
image: hellodockertools
build:
context: .
dockerfile: HelloDockerTools\Dockerfile
Depuración
Seleccione Docker en la lista desplegable de depuración de la barra de herramientas y empiece a depurar la
aplicación. La vista Docker de la ventana Salida muestra las acciones siguientes en curso:
Se adquiere la imagen microsoft/aspnetcore en tiempo de ejecución (si todavía no está en la caché).
Se adquiere la imagen de compilación o publicación microsoft/aspnetcore-build (si todavía no está en la caché).
La variable de entorno ASPNETCORE_ENVIRONMENT se establece en Development dentro del contenedor.
Se expone el puerto 80 y se asigna a un puerto asignado dinámicamente para el host local. El puerto viene
determinado por el host de Docker y se puede consultar con el comando docker ps .
La aplicación se copia en el contenedor.
Se inicia el explorador predeterminado con el depurador asociado al contenedor, con el puerto asignado
dinámicamente.
La imagen de Docker resultante es la imagen dev de la aplicación, con las imágenes microsoft/aspnetcore como
imagen base. Ejecute el comando docker images en la ventana Consola del Administrador de paquetes (PMC ).
Se muestran las imágenes en la máquina:
NOTE
La imagen de desarrollo carece del contenido de la aplicación, ya que las configuraciones de depuración usan el montaje de
volumen para proporcionar la experiencia iterativa. Para insertar una imagen, use la configuración de versión.
Ejecute el comando docker ps en la PMC. Tenga en cuenta que la aplicación se ejecuta mediante el contenedor:
Editar y continuar
Los cambios en archivos estáticos y vistas de Razor se actualizan automáticamente sin necesidad de ningún paso
de compilación. Realice el cambio, guarde y actualice el explorador para ver la actualización.
Las modificaciones en archivos de código requieren compilación y un reinicio del Kestrel dentro del contenedor.
Después de realizar la modificación, presione CTRL + F5 para realizar el proceso e iniciar la aplicación dentro del
contenedor. El contenedor de Docker no se vuelve a compilar ni se detiene. Ejecute el comando docker ps en la
PMC. Observe que el contenedor original se está ejecutando desde hace 10 minutos:
Podría esperarse que la imagen de producción o versión fuera más pequeña que la imagen dev. Debido a la
asignación de volumen, el depurador y la aplicación se han ejecutado desde la máquina local y no dentro del
contenedor. La imagen más reciente ha empaquetado el código de aplicación necesario para ejecutar la aplicación
en un equipo host. Por tanto, la diferencia es el tamaño del código de aplicación.
Configuración de ASP.NET Core para trabajar con
servidores proxy y equilibradores de carga
31/05/2018 • 18 minutes to read • Edit Online
Encabezados reenviados
Por costumbre, los servidores proxy reenvían la información en encabezados HTTP.
HEADER DESCRIPTION
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
}
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
// In ASP.NET Core 1.x, replace the following line with: app.UseIdentity();
app.UseAuthentication();
app.UseMvc();
}
NOTE
Si no se especifica ningún valor ForwardedHeadersOptions en Startup.ConfigureServices o directamente para el
método de extensión con UseForwardedHeaders(IApplicationBuilder, ForwardedHeadersOptions), los encabezados
predeterminados que se reenvían son ForwardedHeaders.None. La propiedad
ForwardedHeadersOptions.ForwardedHeaders se debe configurar con los encabezados que se reenvían.
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardLimit = 2;
options.KnownProxies.Add(IPAddress.Parse("127.0.10.1"));
options.ForwardedForHeaderName = "X-Forwarded-For-Custom-Header-Name";
});
OPCIÓN DESCRIPTION
El valor predeterminado es 1.
OPCIÓN DESCRIPTION
El valor predeterminado es 1.
Este código puede deshabilitarse con una variable de entorno u otro valor de configuración en un entorno de
desarrollo o ensayo.
Tratar con la ruta de acceso base y los servidores proxy que cambian la ruta de acceso de la solicitud
Algunos servidores proxy pasan la ruta de acceso sin cambios pero con una ruta de acceso base de aplicación
que se debe quitar para que el enrutamiento funcione correctamente. El middleware
UsePathBaseExtensions.UsePathBase divide la ruta de acceso en HttpRequest.Path y la ruta de acceso base de
aplicación en HttpRequest.PathBase.
Si es la ruta de acceso base de aplicación para una ruta de acceso de proxy que se pasa como
/foo
/foo/api/1, el middleware establece Request.PathBase en /foo y Request.Path en /api/1 con el siguiente
comando:
app.UsePathBase("/foo");
La ruta de acceso base y la ruta de acceso original se vuelven a aplicar cuando se llama de nuevo al middleware
en orden inverso. Para más información sobre el procesamiento de pedidos de middleware, consulte
Middleware.
Si el proxy recorta la ruta de acceso (por ejemplo, el reenvío /foo/api/1 a /api/1 ), corrija los
redireccionamientos y los vínculos mediante el establecimiento de la propiedad PathBase de la solicitud:
app.Use((context, next) =>
{
context.Request.PathBase = new PathString("/foo");
return next();
});
Si el proxy va a agregar datos de ruta de acceso, descarte parte de esta ruta para corregir los
redireccionamientos y los vínculos; para ello, use StartsWithSegments(PathString, PathString) y asígnelo a la
propiedad Path:
return next();
});
Solucionar problemas
Cuando no se reenvíen los encabezados como estaba previsto, habilite el registro. Si los registros no
proporcionan suficiente información para solucionar el problema, enumere los encabezados de solicitud
recibidos por el servidor. Los encabezados se pueden escribir en una respuesta de aplicación mediante
middleware insertado:
// Headers
await context.Response.WriteAsync($"Request Headers:{Environment.NewLine}");
await context.Response.WriteAsync(Environment.NewLine);
// Connection: RemoteIp
await context.Response.WriteAsync(
$"Request RemoteIp: {context.Connection.RemoteIpAddress}");
}
}
Asegúrese de que el servidor reciba los encabezados X-Forwarded-* con los valores esperados. Si hay varios
valores en un encabezado determinado, observe que el Middleware de encabezados reenviados procesa los
encabezados en orden inverso, de derecha a izquierda.
La dirección IP remota original de la solicitud debe coincidir con una entrada de las listas KnownProxies o
KnownNetworks antes de procesar X -Forwarded-For. Esto limita la suplantación de encabezados al no aceptarse
reenviadores de servidores proxy que no son de confianza.
Recursos adicionales
Microsoft Security Advisory CVE -2018-0787: ASP.NET Core Elevation Of Privilege Vulnerability (Microsoft
Security Advisory CVE -2018-0787: Vulnerabilidad de elevación de privilegios de ASP.NET Core)
Perfiles de publicación de Visual Studio para la
implementación de aplicaciones ASP.NET Core
25/06/2018 • 22 minutes to read • Edit Online
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
</ItemGroup>
</Project>
El atributo <Project> del elemento Sdk lleva a cabo las siguientes tareas:
Importa el archivo de propiedades de $ (MSBuildSDKsPath)\Microsoft.NET.Sdk.Web\Sdk\Sdk.Props al
comienzo.
Importa el archivo targets de $ (MSBuildSDKsPath)\Microsoft.NET.Sdk.Web\Sdk\Sdk.targets al final.
La ubicación predeterminada de MSBuildSDKsPath (con Visual Studio 2017 Enterprise) es la carpeta
%programfiles(x86 )%\Microsoft Visual Studio\2017\Enterprise\MSBuild\Sdks.
El SDK de Microsoft.NET.Sdk.Web depende de:
Microsoft.NET.Sdk.Web.ProjectSystem
Microsoft.NET.Sdk.Publish
Lo que hace que se importen las propiedades y los destinos siguientes:
$ (MSBuildSDKsPath)\Microsoft.NET.Sdk.Web.ProjectSystem\Sdk\Sdk.Props
$ (MSBuildSDKsPath)\Microsoft.NET.Sdk.Web.ProjectSystem\Sdk\Sdk.targets
$ (MSBuildSDKsPath)\Microsoft.NET.Sdk.Publish\Sdk\Sdk.Props
$ (MSBuildSDKsPath)\Microsoft.NET.Sdk.Publish\Sdk\Sdk.targets
Los destinos de publicación importan el conjunto adecuado de destinos en función del método de publicación
usado.
Cuando se carga un proyecto en Visual Studio o MSBuild, se llevan a cabo las siguientes acciones generales:
Compilación del proyecto
Cálculo de los archivos para la publicación
Publicación de los archivos en el destino
La lista de elementos Content contiene archivos que se van a publicar, además de los resultados de compilación.
De forma predeterminada, los archivos que coinciden con el patrón wwwroot/** se incluyen en el elemento
Content . El wwwroot/\*\* patrón global coincide con los archivos de la carpeta wwwroot y de las subcarpetas y.
Para agregar explícitamente un archivo a la lista de publicación, agregue el archivo directamente en el archivo
.csproj, como se muestra en Archivos de inclusión.
Al seleccionar el botón Publicar en Visual Studio o al publicar desde la línea de comandos:
Se calculan los elementos/propiedades (los archivos que se deben compilar).
Solo para Visual Studio: se restauran los paquetes NuGet. (la restauración debe ser explícita por parte del
usuario en la CLI).
Se compila el proyecto.
Se calculan los elementos de publicación (los archivos que se deben publicar).
Se publica el proyecto (los archivos calculados se copian en el destino de publicación).
Cuando hace referencia a un proyecto de ASP.NET Core Microsoft.NET.Sdk.Web en el archivo de proyecto, se
coloca un archivo app_offline.htm en la raíz del directorio de la aplicación web. Cuando el archivo está presente, el
módulo de ASP.NET Core cierra correctamente la aplicación y proporciona el archivo app_offline.htm durante la
implementación. Para más información, vea ASP.NET Core Module configuration reference (Referencia de
configuración del módulo de ASP.NET Core).
Ejecute los comandos siguientes para crear y publicar una aplicación web:
ASP.NET Core 2.x
ASP.NET Core 1.x
El comando dotnet publish llama a MSBuild, que invoca el destino Publish . Todos los parámetros pasados a
dotnet publish se pasan a MSBuild. El parámetro -c se asigna a la propiedad de MSBuild Configuration . El
parámetro -o se asigna a OutputPath .
Se pueden pasar propiedades de MSBuild mediante cualquiera de los siguientes formatos:
p:<NAME>=<VALUE>
/p:<NAME>=<VALUE>
El recurso compartido de red se especifica con barras diagonales (//r8/) y funciona en todas las plataformas
compatibles de .NET Core.
Confirme que la aplicación publicada para la implementación no se está ejecutando. Los archivos de la carpeta
publish quedan bloqueados mientras se ejecuta la aplicación. No se puede llevar a cabo la implementación porque
no se pueden copiar los archivos bloqueados.
El paquete de MSDeploy (actualmente solo funciona en Windows dado que MSDeploy no es multiplataforma):
<Project>
<PropertyGroup>
<PublishProtocol>Kudu</PublishProtocol>
<PublishSiteName>nodewebapp</PublishSiteName>
<UserName>username</UserName>
<Password>password</Password>
</PropertyGroup>
</Project>
Ejecute el comando siguiente para descomprimir el contenido de publicación y publicarlo en Azure mediante las
API de Kudu:
Por ejemplo, al efectuar una publicación con un perfil denominado FolderProfile, se puede ejecutar cualquiera de
los comandos siguientes:
dotnet build /p:DeployOnBuild=true /p:PublishProfile=FolderProfile
msbuild /p:DeployOnBuild=true /p:PublishProfile=FolderProfile
Al invocar dotnet build, se llama a msbuild para ejecutar el proceso de compilación y publicación. Llamar a
dotnet build o msbuild es equivalente cuando se pasa un perfil de carpeta. Al llamar a MSBuild directamente en
Windows, se usa la versión .NET Framework de MSBuild. Actualmente, MSDeploy está limitado a los equipos con
Windows para efectuar publicaciones. Al llamar a dotnet build en un perfil que no es de carpeta se invoca a
MSBuild, que usa MSDeploy en los perfiles que no son de carpeta. Al llamar a dotnet build en un perfil que no
es de carpeta, se invoca a MSBuild (mediante MSDeploy), lo cual genera un error (incluso si se ejecuta en una
plataforma de Windows). Para publicar con un perfil que no es de carpeta, llame directamente a MSBuild.
El siguiente perfil de publicación de carpeta se creó con Visual Studio y se publica en un recurso compartido de
red:
Tenga en cuenta que <LastUsedBuildConfiguration> está establecido en Release . Al efectuar una publicación en
Visual Studio, el valor de la propiedad de configuración <LastUsedBuildConfiguration> se establece con el valor
cuando se inicia el proceso de publicación. La propiedad de configuración <LastUsedBuildConfiguration> es
especial y no se debe reemplazar en un archivo importado de MSBuild. Esta propiedad se puede invalidar desde
la línea de comandos.
Con la CLI de .NET Core:
Con MSBuild:
Archivos de exclusión
Al publicar aplicaciones web de ASP.NET Core, se incluyen los artefactos de compilación y el contenido de la
carpeta wwwroot. msbuild admite los patrones globales. Por ejemplo, el siguiente elemento <Content> excluye
todo los archivos de texto (.txt) de la carpeta wwwroot/content y de todas sus subcarpetas.
<ItemGroup>
<Content Update="wwwroot/content/**/*.txt" CopyToPublishDirectory="Never" />
</ItemGroup>
El marcado anterior se puede agregar a un perfil de publicación o al archivo .csproj. Si se agrega al archivo .csproj,
la regla se agrega a todos los perfiles de publicación del proyecto.
El siguiente elemento <MsDeploySkipRules> excluye todos los archivos de la carpeta wwwroot/content:
<ItemGroup>
<MsDeploySkipRules Include="CustomSkipFolder">
<ObjectName>dirPath</ObjectName>
<AbsolutePath>wwwroot\\content</AbsolutePath>
</MsDeploySkipRules>
</ItemGroup>
<MsDeploySkipRules> no eliminará del sitio de implementación los destinos skip. Los archivos y carpetas
destinados a <Content> se eliminan del sitio de implementación. Por ejemplo, suponga que una aplicación web
implementada tenía los siguientes archivos:
Views/Home/About1.cshtml
Views/Home/About2.cshtml
Views/Home/About3.cshtml
Si se agregan los siguientes elementos <MsDeploySkipRules> , esos archivos no se eliminarán en el sitio de
implementación.
<ItemGroup>
<MsDeploySkipRules Include="CustomSkipFile">
<ObjectName>filePath</ObjectName>
<AbsolutePath>Views\\Home\\About1.cshtml</AbsolutePath>
</MsDeploySkipRules>
<MsDeploySkipRules Include="CustomSkipFile">
<ObjectName>filePath</ObjectName>
<AbsolutePath>Views\\Home\\About2.cshtml</AbsolutePath>
</MsDeploySkipRules>
<MsDeploySkipRules Include="CustomSkipFile">
<ObjectName>filePath</ObjectName>
<AbsolutePath>Views\\Home\\About3.cshtml</AbsolutePath>
</MsDeploySkipRules>
</ItemGroup>
Los elementos <MsDeploySkipRules> anteriores impiden que se implementen los archivos omitidos. Una vez que
se implementan esos archivos, no se eliminarán.
El siguiente elemento <Content> elimina los archivos de destino en el sitio de implementación:
<ItemGroup>
<Content Update="Views/Home/About?.cshtml" CopyToPublishDirectory="Never" />
</ItemGroup>
El uso de la implementación de línea de comandos con el elemento <Content> anterior, produce la siguiente
salida:
MSDeployPublish:
Starting Web deployment task from source:
manifest(C:\Webs\Web1\obj\Release\netcoreapp1.1\PubTmp\Web1.SourceManifest.
xml) to Destination: auto().
Deleting file (Web11112\Views\Home\About1.cshtml).
Deleting file (Web11112\Views\Home\About2.cshtml).
Deleting file (Web11112\Views\Home\About3.cshtml).
Updating file (Web11112\web.config).
Updating file (Web11112\Web1.deps.json).
Updating file (Web11112\Web1.dll).
Updating file (Web11112\Web1.pdb).
Updating file (Web11112\Web1.runtimeconfig.json).
Successfully executed Web deployment task.
Publish Succeeded.
Done Building Project "C:\Webs\Web1\Web1.csproj" (default targets).
Archivos de inclusión
El siguiente marcado incluye una carpeta images fuera del directorio del proyecto a la carpeta wwwroot/images
del sitio de publicación:
<ItemGroup>
<_CustomFiles Include="$(MSBuildProjectDirectory)/../images/**/*" />
<DotnetPublishFiles Include="@(_CustomFiles)">
<DestinationRelativePath>wwwroot/images/%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
</DotnetPublishFiles>
</ItemGroup>
El marcado se puede agregar al archivo .csproj o al perfil de publicación. Si se agrega al archivo .csproj, se incluye
en todos los perfiles de publicación del proyecto.
En el siguiente marcado resaltado se muestra cómo:
Copiar un archivo de fuera del proyecto a la carpeta wwwroot.
Excluir la carpeta wwwroot\Content.
Excluir Views\Home\About2.cshtml.
</ItemGroup>
</Project>
Vea WebSDK Readme (Archivo Léame de WebSDK) para ver más ejemplos de implementación.
Servicio Kudu
Para ver los archivos de la implementación de una aplicación web de Azure App Service, use el servicio Kudu.
Anexe el token scm al nombre de la aplicación web. Por ejemplo:
RESOLUCIÓN RESULTADO
Seleccione el elemento de menú Consola de depuración para ver, editar, eliminar o agregar archivos.
Recursos adicionales
Web Deploy (MSDeploy) simplifica la implementación de aplicaciones web y sitios web en servidores de IIS.
https://github.com/aspnet/websdk: problemas de archivos y características de solicitud para la
implementación.
Estructura de directorios de ASP.NET Core
25/06/2018 • 3 minutes to read • Edit Online
†Indica un directorio
El directorio publish representa la ruta de acceso raíz del contenido, también conocida como la ruta de acceso
base de aplicación, de la implementación. Sea cual sea el nombre que se asigna al directorio publish de la
aplicación implementada en el servidor, su ubicación funciona como la ruta física del servidor a la aplicación
hospedada.
El directorio wwwroot, si existe, solo contiene recursos estáticos.
El directorio registros de stdout se puede crear para la implementación mediante una de las dos estrategias
siguientes:
Agregue el siguiente elemento <Target> al archivo del proyecto:
El elemento <MakeDir> crea una carpeta Logs vacía en la salida publicada. El elemento usa la propiedad
PublishDir para determinar la ubicación de destino para la creación de la carpeta. Varios métodos de
implementación, como Web Deploy, omiten las carpetas vacías durante la implementación. El elemento
<WriteLinesToFile> genera un archivo en la carpeta Logs, que garantiza la implementación de la carpeta
en el servidor. Tenga en cuenta que puede producirse un error en la creación de carpetas si el proceso de
trabajo no tiene acceso de escritura a la carpeta de destino.
Cree físicamente el directorio Logs en el servidor de la implementación.
El directorio de implementación requiere permisos de lectura y ejecución. El directorio Logs requiere permisos
de lectura y escritura. Otros directorios donde se escriben los archivos requieren permisos de lectura y
escritura.
Referencia de errores comunes de Azure App
Service e IIS con ASP.NET Core
25/06/2018 • 19 minutes to read • Edit Online
IMPORTANT
Aviso sobre el uso de las versiones preliminares de ASP.NET Core 2.1
Consulte Implementar una versión preliminar de ASP.NET Core en Azure App Service.
ASP.NET Core permite a los desarrolladores configurar y administrar con facilidad la seguridad de sus aplicaciones.
ASP.NET Core contiene características para administrar la autenticación, autorización, protección de datos,
cumplimiento de SSL, secretos de aplicación, protección contra falsificación de solicitudes y administración de
CORS. Estas características de seguridad permiten compilar aplicaciones de ASP.NET Core sólidas y seguras.
Por Pranav Rastogi, Rick Anderson, Tom Dykstra, Jon Galloway, Erik Reitan, y Steve Smith
Identidad de ASP.NET Core es un sistema de pertenencia que le permite agregar funcionalidad de inicio de
sesión a la aplicación. Los usuarios pueden crear una cuenta y el inicio de sesión con un nombre de usuario y
contraseña o se puede usar un proveedor de inicio de sesión externo como Facebook, Google, Microsoft
Account, Twitter u otras personas.
Puede configurar ASP.NET Core Identity para utilizar una base de datos de SQL Server para almacenar
nombres de usuario, contraseñas y datos de perfil. Como alternativa, puede usar su propio almacén
persistente, por ejemplo, un almacenamiento de tablas de Azure. Este documento contiene instrucciones para
Visual Studio y para el uso de la CLI.
Ver o descargar el código de ejemplo. (Cómo descargar)
Un cuadro de diálogo aparece oferta las opciones de autenticación. Seleccione cuentas de usuario
individuales y haga clic en Aceptar para volver al cuadro de diálogo anterior.
Seleccionar cuentas de usuario individuales indica a Visual Studio para crear modelos, ViewModels,
vistas, controladores y otros recursos necesarios para la autenticación como parte de la plantilla de
proyecto.
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = false;
options.Password.RequiredUniqueChars = 6;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.AllowedForNewUsers = true;
// User settings
options.User.RequireUniqueEmail = true;
});
services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
// If the LoginPath isn't set, ASP.NET Core defaults
// the path to /Account/Login.
options.LoginPath = "/Account/Login";
// If the AccessDeniedPath isn't set, ASP.NET Core defaults
// the path to /Account/AccessDenied.
options.AccessDeniedPath = "/Account/AccessDenied";
options.SlidingExpiration = true;
});
services.AddMvc();
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
Para obtener más información sobre el proceso de inicio aplicación, consulte inicio de la aplicación.
3. Cree un usuario.
Inicie la aplicación y, a continuación, haga clic en el registrar vínculo.
Si se trata de la primera vez que se va a realizar esta acción, puede ser necesario para ejecutar las
migraciones. La aplicación le pide que migraciones aplicar. Si es necesario, actualice la página.
Como alternativa, puede probar mediante ASP.NET Core Identity con la aplicación sin una base de
datos persistente desde una base de datos en memoria. Para usar una base de datos en memoria,
agregue el Microsoft.EntityFrameworkCore.InMemory el paquete a la aplicación y modificar la llamada de
la aplicación a AddDbContext en ConfigureServices como se indica a continuación:
services.AddDbContext<ApplicationDbContext>(options =>
options.UseInMemoryDatabase(Guid.NewGuid().ToString()));
Cuando el usuario hace clic en el registrar vínculo, el Register acción se invoca en AccountController
. El Register acción crea el usuario mediante una llamada a CreateAsync en el _userManager objeto
(proporcionado a AccountController por inyección de dependencia):
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
// For more information on how to enable account confirmation and password reset please
visit http://go.microsoft.com/fwlink/?LinkID=532713
// Send an email with this link
//var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
//var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code
= code }, protocol: HttpContext.Request.Scheme);
//await _emailSender.SendEmailAsync(model.Email, "Confirm your account",
// "Please confirm your account by clicking this link: <a href=\"" + callbackUrl +
"\">link</a>");
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation(3, "User created a new account with password.");
return RedirectToAction(nameof(HomeController.Index), "Home");
}
AddErrors(result);
}
La base de Controller clase expone un User propiedad que se puede acceder desde los métodos de
controlador. Por ejemplo, puede enumerar User.Claims y tomar decisiones de autorización. Para
obtener más información, consulte autorización.
5. Cierre sesión.
Al hacer clic en el cerrar sesión vincular llamadas el LogOut acción.
//
// POST: /Account/LogOut
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> LogOut()
{
await _signInManager.SignOutAsync();
_logger.LogInformation(4, "User logged out.");
return RedirectToAction(nameof(HomeController.Index), "Home");
}
6. Configuración.
Identidad tiene algunos comportamientos predeterminados que se pueden invalidar en la clase de inicio de la
aplicación. IdentityOptions no es necesario configurar al utilizar los comportamientos predeterminados. El
código siguiente establece varias opciones de seguridad de contraseña:
ASP.NET Core 2.x
ASP.NET Core 1.x
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = false;
options.Password.RequiredUniqueChars = 6;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.AllowedForNewUsers = true;
// User settings
options.User.RequireUniqueEmail = true;
});
services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
// If the LoginPath isn't set, ASP.NET Core defaults
// the path to /Account/Login.
options.LoginPath = "/Account/Login";
// If the AccessDeniedPath isn't set, ASP.NET Core defaults
// the path to /Account/AccessDenied.
options.AccessDeniedPath = "/Account/AccessDenied";
options.SlidingExpiration = true;
});
services.AddMvc();
}
Para obtener más información acerca de cómo configurar la identidad, vea configurar identidad.
También puede configurar el tipo de datos de la clave principal, vea tipo de datos de las claves principales de
configurar la identidad.
7. Ver la base de datos.
Si la aplicación usa una base de datos de SQL Server (el valor predeterminado en Windows y para
usuarios de Visual Studio), puede ver la base de datos de la aplicación creada. Puede usar SQL Server
Management Studio. O bien, desde Visual Studio, seleccione vista > Explorador de objetos de
SQL Server. Conectarse a (localdb) \MSSQLLocalDB. La base de datos con un nombre que
coincida con aspnet - <nombre del proyecto>-<cadena de fecha > se muestra.
Expanda la base de datos y su tablas, a continuación, haga clic en el dbo. AspNetUsers de tabla y
seleccione ver datos.
8. Compruebe que funciona de identidad
El valor predeterminado aplicación Web de ASP.NET Core plantilla de proyecto permite a los usuarios
tener acceso a cualquier acción en la aplicación sin necesidad de inicio de sesión. Para comprobar que
funciona ASP.NET Identity, agregue un [Authorize] atribuir a la About acción de la Home controlador.
[Authorize]
public IActionResult About()
{
ViewData["Message"] = "Your application description page.";
return View();
}
Visual Studio
CLI de .NET Core
Ejecutar el proyecto mediante Ctrl + F5 y navegue hasta la sobre página. Solo los usuarios
autenticados pueden tener acceso a la sobre página ahora, por lo que ASP.NET le redirige a la página
de inicio de sesión para iniciar sesión o regístrese.
Componentes de identidad
El ensamblado de referencia principal para el sistema de identidades Microsoft.AspNetCore.Identity . Este
paquete contiene el conjunto básico de interfaces para ASP.NET Core Identity y se incluye por
Microsoft.AspNetCore.Identity.EntityFrameworkCore .
Estas dependencias necesarios para usar el sistema de identidades en aplicaciones de ASP.NET Core:
Microsoft.AspNetCore.Identity.EntityFrameworkCore -Contiene los tipos necesarios para usar la
identidad con Entity Framework Core.
Microsoft.EntityFrameworkCore.SqlServer-Entity Framework Core es la tecnología de acceso a datos
recomendado de Microsoft para las bases de datos relacional como SQL Server. Para las pruebas,
puede usar Microsoft.EntityFrameworkCore.InMemory .
Microsoft.AspNetCore.Authentication.Cookies -Middleware que permite que una aplicación utilizar la
autenticación basada en cookies.
Pasos siguientes
Migrar de autenticación e identidad
Confirmación de cuentas y recuperación de contraseñas
Autenticación en dos fases con SMS
Facebook, Google y la autenticación de proveedor externo.
Identidad de scaffolding en proyectos de ASP.NET
Core
22/06/2018 • 18 minutes to read • Edit Online
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc();
}
}
UseHsts is recommended but not required. See HTTP Strict Transport Security Protocol for more information.
Requiere que el código de base de datos de identidad generado migraciones de Entity Framework Core. Cree una
migración y actualice la base de datos. Por ejemplo, ejecute los siguientes comandos:
Visual Studio
CLI de .NET Core
En Visual Studio consola de administrador de paquetes:
Add-Migration CreateIdentitySchema
Update-Database
Add-Migration CreateIdentitySchema
Update-Database
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc();
}
}
UseHsts is recommended but not required. See HTTP Strict Transport Security Protocol for more information.
Cambios de diseño
Opcional: Agregar el inicio de sesión parcial ( _LoginPartial ) para el archivo de diseño:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - RazorNoAuth8</title>
<environment include="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
</environment>
<environment exclude="Development">
<link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-
value="absolute" />
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
</environment>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-
collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a asp-page="/Index" class="navbar-brand">RazorNoAuth8</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a asp-page="/Index">Home</a></li>
<li><a asp-page="/About">About</a></li>
<li><a asp-page="/Contact">Contact</a></li>
</ul>
<partial name="_LoginPartial" />
</div>
</div>
</nav>
<environment include="Development">
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
</environment>
<environment exclude="Development">
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.3.1.min.js"
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery"
crossorigin="anonymous"
integrity="sha384-K+ctZQ+LL8q6tP7I94W+qzQsfRV2a+AfHIi9k8z8l9ggpc8X+Ytst4yBo/hH+8Fk">
</script>
<script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
crossorigin="anonymous"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa">
</script>
</script>
<script src="~/js/site.min.js" asp-append-version="true"></script>
</environment>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - MvcNoAuth3</title>
<environment include="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
</environment>
<environment exclude="Development">
<link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-
value="absolute" />
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
</environment>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-
collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a asp-area="" asp-controller="Home" asp-action="Index" class="navbar-brand">MvcNoAuth3</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li>
<li><a asp-area="" asp-controller="Home" asp-action="About">About</a></li>
<li><a asp-area="" asp-controller="Home" asp-action="Contact">Contact</a></li>
</ul>
<partial name="_LoginPartial" />
</div>
</div>
</nav>
<environment include="Development">
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
</environment>
<environment exclude="Development">
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.3.1.min.js"
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery"
crossorigin="anonymous"
integrity="sha384-K+ctZQ+LL8q6tP7I94W+qzQsfRV2a+AfHIi9k8z8l9ggpc8X+Ytst4yBo/hH+8Fk">
</script>
<script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"
<script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
crossorigin="anonymous"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa">
</script>
<script src="~/js/site.min.js" asp-append-version="true"></script>
</environment>
Add-Migration CreateIdentitySchema
Update-Database
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvcWithDefaultRoute();
}
}
UseHsts is recommended but not required. See HTTP Strict Transport Security Protocol for more information.
Identidad de scaffolding en un proyecto MVC con autorización
Ejecute al scaffolder de identidad:
Visual Studio
CLI de .NET Core
De el Explorador de soluciones, haga doble clic en el proyecto > agregar > nuevo elemento de
scaffolding.
En el panel izquierdo de la agregar scaffolding cuadro de diálogo, seleccione identidad > agregar.
En el Agregar identidad cuadro de diálogo, seleccione las opciones que desee.
Seleccione la página de diseño existente, o se sobrescribirá el archivo de diseño con formato incorrecto.
Cuando se selecciona un archivo _Layout.cshtml existente, es no sobrescribe.
Por ejemplo ~/Pages/Shared/_Layout.cshtml para las páginas de Razor ~/Views/Shared/_Layout.cshtml para los
proyectos MVC
Para usar el contexto de datos existente, seleccione al menos un archivo para invalidar. Debe seleccionar al
menos un archivo para agregar el contexto de datos.
Seleccione la clase de contexto de datos.
Seleccione agregar.
Para crear un nuevo contexto de usuario y posiblemente crear una clase de usuario personalizado de identidad:
Seleccione el + botón para crear un nuevo clase de contexto de datos.
Seleccione agregar.
Nota: Si va a crear un nuevo contexto de usuario, no tienes que seleccionar un archivo para invalidar.
Eliminar el páginas/Shared carpeta y los archivos de esa carpeta.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<IdentityUser, IdentityRole>()
// services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddRazorPagesOptions(options =>
{
options.AllowAreas = true;
options.Conventions.AuthorizeAreaFolder("Identity", "/Account/Manage");
options.Conventions.AuthorizeAreaPage("Identity", "/Account/Logout");
});
services.ConfigureApplicationCookie(options =>
{
options.LoginPath = $"/Identity/Account/Login";
options.LogoutPath = $"/Identity/Account/Logout";
options.AccessDeniedPath = $"/Identity/Account/AccessDenied";
});
// using Microsoft.AspNetCore.Identity.UI.Services;
services.AddSingleton<IEmailSender, EmailSender>();
}
services.AddIdentity<IdentityUser, IdentityRole>()
// services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.ConfigureApplicationCookie(options =>
{
options.LoginPath = $"/Identity/Account/Login";
options.LogoutPath = $"/Identity/Account/Logout";
options.AccessDeniedPath = $"/Identity/Account/AccessDenied";
});
// using Microsoft.AspNetCore.Identity.UI.Services;
services.AddSingleton<IEmailSender, EmailSender>();
Agregar, descargar y eliminar datos de usuario
personalizada para la identidad en un proyecto de
ASP.NET Core
22/06/2018 • 10 minutes to read • Edit Online
Requisitos previos
.NET Core 2.1 SDK or later
using Microsoft.AspNetCore.Identity;
using System;
namespace WebApp1.Areas.Identity.Data
{
public class WebApp1User : IdentityUser
{
[PersonalData]
public string Name { get; set; }
[PersonalData]
public DateTime DOB { get; set; }
}
}
[TempData]
public string StatusMessage { get; set; }
[BindProperty]
public InputModel Input { get; set; }
[Required]
[Display(Name = "Birth Date")]
[DataType(DataType.Date)]
public DateTime DOB { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Phone]
[Display(Name = "Phone number")]
public string PhoneNumber { get; set; }
}
Username = userName;
return Page();
}
if (Input.Name != user.Name)
{
user.Name = Input.Name;
}
if (Input.DOB != user.DOB)
{
user.DOB = Input.DOB;
}
await _userManager.UpdateAsync(user);
await _signInManager.RefreshSignInAsync(user);
StatusMessage = "Your profile has been updated";
return RedirectToPage();
}
<h4>@ViewData["Title"]</h4>
@Html.Partial("_StatusMessage", Model.StatusMessage)
<div class="row">
<div class="col-md-6">
<form id="profile-form" method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Username"></label>
<input asp-for="Username" class="form-control" disabled />
</div>
<div class="form-group">
<label asp-for="Input.Email"></label>
@if (Model.IsEmailConfirmed)
{
<div class="input-group">
<input asp-for="Input.Email" class="form-control" />
<span class="input-group-addon" aria-hidden="true"><span class="glyphicon glyphicon-ok
text-success"></span></span>
</div>
}
else
{
<input asp-for="Input.Email" class="form-control" />
<button id="email-verification" type="submit" asp-page-handler="SendVerificationEmail"
class="btn btn-link">Send verification email</button>
}
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-group">
<div class="form-group">
<label asp-for="Input.Name"></label>
<input asp-for="Input.Name" class="form-control" />
</div>
<div class="form-group">
<label asp-for="Input.DOB"></label>
<input asp-for="Input.DOB" class="form-control" />
</div>
<label asp-for="Input.PhoneNumber"></label>
<input asp-for="Input.PhoneNumber" class="form-control" />
<span asp-validation-for="Input.PhoneNumber" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-default">Save</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
[BindProperty]
public InputModel Input { get; set; }
[Required]
[Display(Name = "Birth Date")]
[DataType(DataType.Date)]
public DateTime DOB { get; set; }
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.",
MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
@page
@model RegisterModel
@{
ViewData["Title"] = "Register";
}
<h2>@ViewData["Title"]</h2>
<div class="row">
<div class="col-md-4">
<form asp-route-returnUrl="@Model.ReturnUrl" method="post">
<h4>Create a new account.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Name"></label>
<input asp-for="Input.Name" class="form-control" />
<span asp-validation-for="Input.Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.DOB"></label>
<input asp-for="Input.DOB" class="form-control" />
<span asp-validation-for="Input.DOB" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.Name"></label>
<input asp-for="Input.Name" class="form-control" />
<span asp-validation-for="Input.Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.DOB"></label>
<input asp-for="Input.DOB" class="form-control" />
<span asp-validation-for="Input.DOB" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.ConfirmPassword"></label>
<input asp-for="Input.ConfirmPassword" class="form-control" />
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-default">Register</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
Compile el proyecto.
Agregar una migración de los datos de usuario personalizada
Visual Studio
CLI de .NET Core
En Visual Studio consola de administrador de paquetes:
Add-Migration CustomUserData
Update-Database
Esta página contiene las opciones de autenticación proporcionado por la Comunidad de código abierto de
ASP.NET Core. Esta página se actualiza periódicamente como nuevos proveedores estén disponibles.
NOMBRE DESCRIPCIÓN
Servidor Gluu Enterprise esté listo, abra el software de código fuente para
identidad, tener acceso a la administración de índices (IAM) y
el inicio de sesión único (SSO). Para obtener más información,
consulte el documentación del producto Gluu.
Identidad de ASP.NET Core utiliza la configuración predeterminada para la configuración como directiva de
contraseñas, el tiempo de bloqueo y la configuración de cookies. Esta configuración puede invalidarse en la
aplicación Startup clase.
Opciones de identidad
El IdentityOptions clase representa las opciones que pueden usarse para configurar el sistema de identidades.
Identidad basada en notificaciones
IdentityOptions.ClaimsIdentity especifica la ClaimsIdentityOptions con las propiedades mostradas en la tabla.
Bloqueo
Impide al usuario durante un período de tiempo después de un determinado número de intentos de acceso
erróneos (valor predeterminado: bloqueo de 5 minutos después de 5 intentos de acceso). Una autenticación
correcta restablece el número de intentos de acceso erróneos y restablece el reloj.
En el ejemplo siguiente se muestra los valores predeterminados:
Contraseña
De forma predeterminada, identidad requiere que las contraseñas contengan un carácter en mayúsculas,
caracteres en minúsculas, un dígito y un carácter no alfanumérico. Las contraseñas deben tener al menos seis
caracteres. Opciones de contraseña puede cambiarse en Startup.ConfigureServices .
ASP.NET Core 2.x
ASP.NET Core 1.x
Núcleo de ASP.NET 2.0 agregado la RequiredUniqueChars propiedad. De lo contrario, las opciones son los
mismos que ASP.NET Core 1.x.
inicio de sesión
tokens
IdentityOptions.Tokens especifica la TokenOptions con las propiedades mostradas en la tabla.
PROPERTY DESCRIPCIÓN
Usuario
Configuración de cookies
Configurar la cookie de la aplicación en Startup.ConfigureServices :
ASP.NET Core 2.x
ASP.NET Core 1.x
services.ConfigureApplicationCookie(options =>
{
options.AccessDeniedPath = "/Account/AccessDenied";
options.Cookie.Name = "YourAppCookieName";
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
options.LoginPath = "/Account/Login";
// ReturnUrlParameter requires `using Microsoft.AspNetCore.Authentication.Cookies;`
options.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
options.SlidingExpiration = true;
});
PROPERTY DESCRIPCIÓN
El valor predeterminado es
CookieSecurePolicy.SameAsRequest.
Una marca que indica si la cookie debe ser accesible sólo a los
servidores.
El valor predeterminado es
CookieSecurePolicy.SameAsRequest .
slidingExpiration Cuando sea true, se emite una cookie nueva con una nueva
hora de expiración cuando la cookie actual está en más de
medio a través de la ventana de expiración.
{
"iisSettings": {
"windowsAuthentication": true,
"anonymousAuthentication": false,
"iisExpress": {
"applicationUrl": "http://localhost:52171/",
"sslPort": 0
}
} // additional options trimmed
}
NOTE
De forma predeterminada, los usuarios que no tienen autorización para acceder a una página se le presentará una
respuesta HTTP 403 vacía. El StatusCodePages middleware se puede configurar para proporcionar a los usuarios una
mejor experiencia de "Acceso denegado".
IIS
Si usa IIS, agregue lo siguiente a la ConfigureServices método:
HTTP.sys
Si utiliza HTTP.sys, agregue lo siguiente a la ConfigureServices método:
Suplantación
ASP.NET Core no implementa la suplantación. Las aplicaciones se ejecutan con la identidad de aplicación para
todas las solicitudes, uso de la identidad de proceso o grupo de servidores de aplicación. Si necesita realizar
una acción en nombre del usuario de forma explícita, use WindowsIdentity.RunImpersonated . Ejecutar una sola
acción en este contexto y, a continuación, cierre el contexto.
app.Run(async (context) =>
{
try
{
var user = (WindowsIdentity)context.User.Identity;
await context.Response
.WriteAsync($"User: {user.Name}\tState: {user.ImpersonationLevel}\n");
WindowsIdentity.RunImpersonated(user.AccessToken, () =>
{
var impersonatedUser = WindowsIdentity.GetCurrent();
var message =
$"User: {impersonatedUser.Name}\tState: {impersonatedUser.ImpersonationLevel}";
Tenga en cuenta que RunImpersonated no es compatible con operaciones asincrónicas y no deben usarse para
escenarios complejos. Por ejemplo, el ajuste de las solicitudes de todas o cadenas de middleware no es
compatible ni recomendable.
Configurar el tipo de datos de clave principal de
identidad en ASP.NET Core
22/06/2018 • 3 minutes to read • Edit Online
Identidad de ASP.NET Core le permite configurar el tipo de datos que se utiliza para representar una clave
principal. Identidad utiliza el string tipo de datos de forma predeterminada. Puede invalidar este
comportamiento.
namespace webapptemplate.Models
{
// Add profile data for application users by adding properties to the ApplicationUser class
public class ApplicationUser : IdentityUser<Guid>
{
}
}
2. Crear una implementación personalizada de la IdentityRole clase. Representa el tipo que se usará para crear
objetos de rol. En el ejemplo siguiente, el valor predeterminado string tipo se reemplaza con Guid .
namespace webapptemplate.Models
{
public class ApplicationRole : IdentityRole<Guid>
{
}
}
3. Cree una clase de contexto de base de datos personalizada. Hereda de la clase de contexto de base de datos
de Entity Framework usada para la identidad. El TUser y TRole argumentos hacen referencia a las clases
de usuario y el rol personalizadas creadas en el paso anterior, respectivamente. El Guid se define el tipo de
datos para la clave principal.
namespace webapptemplate.Data
{
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, Guid>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
4. Registre la clase de contexto de base de datos personalizada al agregar el servicio de identidad en la clase
de inicio de la aplicación.
ASP.NET Core 2.x
ASP.NET Core 1.x
El AddEntityFrameworkStores método no aceptar un TKey argumento tal como se hacía en ASP.NET Core
1.x. Tipo de datos de la clave principal se deduce mediante el análisis de la DbContext objeto.
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
[HttpGet]
[AllowAnonymous]
public async Task<Guid> GetCurrentUserId()
{
ApplicationUser user = await _userManager.GetUserAsync(HttpContext.User);
return user.Id; // No need to cast here because user.Id is already a Guid, and not a string
}
Proveedores de almacenamiento personalizados para
ASP.NET Core Identity
22/06/2018 • 20 minutes to read • Edit Online
Introducción
De forma predeterminada, el sistema de identidades de ASP.NET Core almacena información de usuario en una
base de datos de SQL Server con Entity Framework Core. Para muchas aplicaciones, este enfoque funciona bien.
Sin embargo, es preferible utilizar un mecanismo de persistencia diferentes o el esquema de datos. Por ejemplo:
Usa almacenamiento de tablas Azure o en otro almacén de datos.
Las tablas de base de datos tienen una estructura distinta.
Puede que desee utilizar un enfoque de acceso a datos diferentes, como Dapper.
En cada uno de estos casos, puede escribir un proveedor personalizado para su mecanismo de almacenamiento y
conecte dicho proveedor de la aplicación.
Identidad de ASP.NET Core se incluye en las plantillas de proyecto en Visual Studio con la opción de "Cuentas de
usuario individuales".
Cuando se usa la CLI de núcleo. NET, agregue -au Individual :
if(rows > 0)
{
return IdentityResult.Success;
}
return IdentityResult.Failed(new IdentityError { Description = $"Could not insert user {user.Email}." });
}
using System;
namespace CustomIdentityProviderSample.CustomProvider
{
public class ApplicationRole
{
public Guid Id { get; set; } = Guid.NewGuid();
public string Name { get; set; }
}
}
// Identity Services
services.AddTransient<IUserStore<ApplicationUser>, CustomUserStore>();
services.AddTransient<IRoleStore<ApplicationRole>, CustomRoleStore>();
string connectionString = Configuration.GetConnectionString("DefaultConnection");
services.AddTransient<SqlConnection>(e => new SqlConnection(connectionString));
services.AddTransient<DapperUsersTable>();
// additional configuration
}
Referencias
Proveedores de almacenamiento personalizado para identidades de ASP.NET
ASP.NET Core Identity -este repositorio incluye vínculos a la Comunidad mantenida los proveedores de
almacenes.
Autenticación con Facebook, Google y proveedores
externos en ASP.NET Core
21/06/2018 • 7 minutes to read • Edit Online
Permitir que los usuarios inicien sesión con sus credenciales es práctico para los usuarios y transfiere muchas
de las complejidades existentes en la administración del proceso de inicio de sesión a un tercero. Para ver
ejemplos de cómo los inicios de sesión de las redes sociales pueden controlar las conversiones del tráfico y de
clientes, vea los casos prácticos de Facebook y Twitter.
Nota: Los paquetes que se presentan aquí sintetizan en gran parte la complejidad del flujo de autenticación de
OAuth, pero conocer los detalles puede ser necesario a la hora de solucionar problemas. Hay disponibles
numerosos recursos; por ejemplo, vea An Introduction to OAuth 2 (Introducción a OAuth 2) o Understanding
OAuth2 (Descripción de OAuth2). Algunos problemas se pueden resolver examinando el código fuente de
ASP.NET Core de los paquetes de los proveedores.
Nota: Este tutorial se aplica a la versión 2.0 del SDK de ASP.NET Core, que se puede seleccionar en la parte
superior del asistente.
Aplicación de migraciones
Ejecute la aplicación y seleccione el vínculo Iniciar sesión.
Seleccione el vínculo Register as a new user Registrarse como usuario nuevo).
Escriba el correo electrónico y la contraseña de la cuenta nueva y, luego, seleccione Registrarse.
Siga estas instrucciones para aplicar las migraciones.
Requerir SSL
OAuth 2.0 requiere el uso de SSL para la autenticación mediante el protocolo HTTPS.
Nota: Los proyectos creados con plantillas de proyecto de aplicación web o API Web de ASP.NET Core 2.x se
configuran automáticamente para habilitar SSL e iniciarse con una dirección URL https si la opción Cuentas
de usuario individuales estaba seleccionada en el cuadro de diálogo Cambiar autenticación del asistente
de proyectos, como se muestra arriba.
Establezca que SSL sea obligatorio en el sitio con los pasos descritos en el tema Exigir SSL en una
aplicación ASP.NET Core.
services.AddAuthentication()
.AddMicrosoftAccount(microsoftOptions => { ... })
.AddGoogle(googleOptions => { ... })
.AddTwitter(twitterOptions => { ... })
.AddFacebook(facebookOptions => { ... });
Pulse Crear.
Establezca una contraseña válida. Podrá usarla para iniciar sesión con su correo electrónico.
Pasos siguientes
En este artículo se introdujo la autenticación externa y se explicaron los requisitos previos necesarios
para agregar inicios de sesión externos a la aplicación de ASP.NET Core.
Páginas de referencia específicas del proveedor para configurar los inicios de sesión para los
proveedores requeridos por la aplicación.
Programa de instalación de inicio de sesión externo
de Facebook en ASP.NET Core
22/06/2018 • 8 minutes to read • Edit Online
En el seleccionar un producto página, haga clic en Set Up en el inicio de sesión de Facebook tarjeta.
El inicio rápido asistente se iniciará con elegir una plataforma como la primera página. Omitir el
asistente por ahora, haga clic en el configuración vínculo en el menú de la izquierda:
NOTE
El URI /signin-facebook se establece como la devolución de llamada predeterminada del proveedor de autenticación de
Facebook. Puede cambiar el URI de devolución de forma predeterminada al configurar el middleware de autenticación de
Facebook a través de los heredados RemoteAuthenticationOptions.CallbackPath propiedad de la FacebookOptions clase.
Ejecute los comandos siguientes para almacenar de forma segura App ID y App Secret con el Administrador de
secreto:
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication().AddFacebook(facebookOptions =>
{
facebookOptions.AppId = Configuration["Authentication:Facebook:AppId"];
facebookOptions.AppSecret = Configuration["Authentication:Facebook:AppSecret"];
});
Consulte la FacebookOptions referencia de API para obtener más información sobre las opciones de
configuración compatible con la autenticación de Facebook. Opciones de configuración pueden utilizarse para:
Solicitar información diferente sobre el usuario.
Agregue los argumentos de cadena de consulta para personalizar la experiencia de inicio de sesión.
Solución de problemas
ASP.NET Core solo 2.x: identidad si no está configurado mediante una llamada a services.AddIdentity en
ConfigureServices , intenta autenticar se producirá en ArgumentException: se debe proporcionar la opción
'SignInScheme'. La plantilla de proyecto que se usan en este tutorial se asegura de que esto se realiza.
Si la base de datos de sitio no se ha creado mediante la aplicación de la migración inicial, obtendrá error en
una operación de base de datos al procesar la solicitud error. Pulse migraciones aplicar para crear la base de
datos y actualizar para continuar después del error.
Pasos siguientes
En este artículo se ha explicado cómo puede autenticar con Facebook. Puede seguir un enfoque similar
para autenticar con otros proveedores que se enumeran en la página anterior.
Una vez que se publica un sitio web a la aplicación web de Azure, debe restablecer la AppSecret en el
portal para desarrolladores de Facebook.
Establecer el Authentication:Facebook:AppId y Authentication:Facebook:AppSecret como configuración de
la aplicación en el portal de Azure. El sistema de configuración está configurado para leer las claves de las
variables de entorno.
Programa de instalación de inicio de sesión externo
de Twitter con ASP.NET Core
22/06/2018 • 7 minutes to read • Edit Online
Pulse crear una aplicación nueva y rellene la aplicación nombre, descripción públicas y sitio Web (Esto
puede ser temporal hasta que el URI Registre el nombre de dominio):
Escriba el URI de desarrollo con /signin-twitter anexan a la válido URI de redireccionamiento de OAuth
campo (por ejemplo: https://localhost:44320/signin-twitter ). El esquema de autenticación de Twitter
configurado más adelante en este tutorial controlará automáticamente las solicitudes en /signin-twitter ruta
para implementar el flujo de OAuth.
NOTE
El segmento URI /signin-twitter se establece como la devolución de llamada predeterminada del proveedor de
autenticación de Twitter. Puede cambiar el URI de devolución de forma predeterminada al configurar el middleware de
autenticación de Twitter a través de los heredados RemoteAuthenticationOptions.CallbackPath propiedad de la
TwitterOptions clase.
Rellene el resto del formulario y pulse crear su aplicación de Twitter. Se muestran los detalles de la nueva
aplicación:
Al implementar el sitio que necesite volver a visitar el Application Management página y registrar un nuevo
URI público.
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication().AddTwitter(twitterOptions =>
{
twitterOptions.ConsumerKey = Configuration["Authentication:Twitter:ConsumerKey"];
twitterOptions.ConsumerSecret = Configuration["Authentication:Twitter:ConsumerSecret"];
});
services.AddAuthentication()
.AddMicrosoftAccount(microsoftOptions => { ... })
.AddGoogle(googleOptions => { ... })
.AddTwitter(twitterOptions => { ... })
.AddFacebook(facebookOptions => { ... });
Consulte la TwitterOptions referencia de API para obtener más información sobre las opciones de configuración
compatible con autenticación de Twitter. Esto se puede usar para solicitar información diferente sobre el usuario.
Solución de problemas
ASP.NET Core solo 2.x: identidad si no está configurado mediante una llamada a services.AddIdentity en
ConfigureServices , intenta autenticar se producirá en ArgumentException: se debe proporcionar la opción
'SignInScheme'. La plantilla de proyecto que se usan en este tutorial se asegura de que esto se realiza.
Si la base de datos de sitio no se ha creado mediante la aplicación de la migración inicial, obtendrá error en
una operación de base de datos al procesar la solicitud error. Pulse migraciones aplicar para crear la base de
datos y actualizar para continuar después del error.
Pasos siguientes
En este artículo se ha explicado cómo puede autenticar con Twitter. Puede seguir un enfoque similar para
autenticar con otros proveedores que se enumeran en la página anterior.
Una vez que se publica un sitio web a la aplicación web de Azure, debe restablecer la ConsumerSecret en el
portal para desarrolladores de Twitter.
Establecer el Authentication:Twitter:ConsumerKey y Authentication:Twitter:ConsumerSecret como
configuración de la aplicación en el portal de Azure. El sistema de configuración está configurado para leer
las claves de las variables de entorno.
Programa de instalación de inicio de sesión externo
de Google en ASP.NET Core
22/06/2018 • 10 minutes to read • Edit Online
Después de aceptar el cuadro de diálogo, se le redirigirá a la página de la biblioteca que le permite elegir las
características de la aplicación nuevo. Buscar API de Google + en la lista y haga clic en el vínculo para agregar
la característica de API:
Se muestra la página de la API recién agregada. Pulse habilitar para agregar inicio de sesión de Google + en
la característica a la aplicación:
Después de habilitar la API, pulse crear credenciales para configurar los secretos:
Elija:
API de Google +
Servidor Web (por ejemplo, node.js, Tomcat), y
Datos de usuario:
Pulse las credenciales que es necesario? lo que irá al segundo paso de configuración de la aplicación, crear
un identificador de cliente de OAuth 2.0:
Dado que vamos a crear un proyecto de Google + con una sola característica (inicio de sesión), podemos
escribir el mismo nombre para el identificador de cliente de OAuth 2.0 que la que se utilizará para el
proyecto.
Escriba el URI de desarrollo con /signin-google anexan a la URI de redireccionamiento autorizados
campo (por ejemplo: https://localhost:44320/signin-google ). La autenticación de Google configurada más
adelante en este tutorial controlará automáticamente las solicitudes en /signin-google ruta para
implementar el flujo de OAuth.
NOTE
El segmento URI /signin-google se establece como la devolución de llamada predeterminada del proveedor de
autenticación de Google. Puede cambiar el URI de devolución de forma predeterminada al configurar el middleware de
autenticación de Google a través de los heredados RemoteAuthenticationOptions.CallbackPath propiedad de la
GoogleOptions clase.
Los valores para estos tokens se pueden encontrar en el archivo JSON que descargó en el paso anterior en
web.client_id y web.client_secret .
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication().AddGoogle(googleOptions =>
{
googleOptions.ClientId = Configuration["Authentication:Google:ClientId"];
googleOptions.ClientSecret = Configuration["Authentication:Google:ClientSecret"];
});
services.AddAuthentication()
.AddMicrosoftAccount(microsoftOptions => { ... })
.AddGoogle(googleOptions => { ... })
.AddTwitter(twitterOptions => { ... })
.AddFacebook(facebookOptions => { ... });
Consulte la GoogleOptions referencia de API para obtener más información sobre las opciones de configuración
compatible con autenticación de Google. Esto se puede usar para solicitar información diferente sobre el usuario.
Solución de problemas
Si recibe un 403 (Forbidden) página de error de la propia aplicación cuando se ejecuta en modo de desarrollo
(o interrupción en el depurador con el mismo error), asegúrese de que API de Google + se ha habilitado en el
biblioteca del Administrador de la API siguiendo los pasos enumerados anteriores en esta página. Si el
inicio de sesión no funciona y no reciben los errores, cambie al modo de desarrollo para que sea más fácil
depurar el problema.
ASP.NET Core solo 2.x: identidad si no está configurado mediante una llamada a services.AddIdentity en
ConfigureServices , intenta autenticar se producirá en ArgumentException: se debe proporcionar la opción
'SignInScheme'. La plantilla de proyecto que se usan en este tutorial se asegura de que esto se realiza.
Si la base de datos de sitio no se ha creado mediante la aplicación de la migración inicial, obtendrá error en
una operación de base de datos al procesar la solicitud error. Pulse migraciones aplicar para crear la base de
datos y actualizar para continuar después del error.
Pasos siguientes
En este artículo se ha explicado cómo puede autenticar con Google. Puede seguir un enfoque similar para
autenticar con otros proveedores que se enumeran en la página anterior.
Una vez que se publica un sitio web a la aplicación web de Azure, debe restablecer la ClientSecret en la
consola de API de Google.
Establecer el Authentication:Google:ClientId y Authentication:Google:ClientSecret como configuración de
la aplicación en el portal de Azure. El sistema de configuración está configurado para leer las claves de las
variables de entorno.
Programa de instalación de Microsoft Account inicio
de sesión externo con ASP.NET Core
22/06/2018 • 9 minutes to read • Edit Online
Si ya no tiene una cuenta de Microsoft, pulse crear uno. Después de iniciar sesión se le redirigirá a mis
aplicaciones página:
Pulse agregar una aplicación en la esquina superior derecha de las esquinas y escriba su nombre de la
aplicación y correo electrónico de contacto:
Para los fines de este tutorial, desactive el el programa de instalación interactiva casilla de verificación.
Pulse crear para continuar la registro página. Proporcionar un nombre y anote el valor de la Id. de
aplicación, que se utiliza como ClientId más adelante en el tutorial:
Pulse Agregar plataforma en el plataformas sección y seleccione el Web plataforma:
En el nuevo Web plataforma sección, escriba la dirección URL de desarrollo con /signin-microsoft anexan a
la redirigir direcciones URL campo (por ejemplo: https://localhost:44320/signin-microsoft ). El esquema de
autenticación de Microsoft configurado más adelante en este tutorial controlará automáticamente las
solicitudes en /signin-microsoft ruta para implementar el flujo de OAuth:
NOTE
El segmento URI /signin-microsoft se establece como la devolución de llamada predeterminada del proveedor de
autenticación de Microsoft. Puede cambiar el URI de devolución de forma predeterminada al configurar el middleware de
autenticación de Microsoft a través de los heredados RemoteAuthenticationOptions.CallbackPath propiedad de la
MicrosoftAccountOptions clase.
Pulse agregar dirección URL para asegurarse de que se agregó la dirección URL.
Rellene cualquier configuración de la aplicación si es necesario y pulse guardar en la parte inferior de la
página para guardar los cambios en la configuración de la aplicación.
Al implementar el sitio que necesite volver a visitar el registro página y establezca una nueva dirección
URL pública.
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication().AddMicrosoftAccount(microsoftOptions =>
{
microsoftOptions.ClientId = Configuration["Authentication:Microsoft:ApplicationId"];
microsoftOptions.ClientSecret = Configuration["Authentication:Microsoft:Password"];
});
services.AddAuthentication()
.AddMicrosoftAccount(microsoftOptions => { ... })
.AddGoogle(googleOptions => { ... })
.AddTwitter(twitterOptions => { ... })
.AddFacebook(facebookOptions => { ... });
Aunque la terminología utilizada en el Portal para desarrolladores de Microsoft nombres estos tokens
ApplicationId y Password , se halle expuestos como ClientId y ClientSecret a la API de configuración.
Consulte la MicrosoftAccountOptions referencia de API para obtener más información sobre las opciones de
configuración compatible con autenticación de Microsoft Account. Esto se puede usar para solicitar información
diferente sobre el usuario.
Al hacer clic en Microsoft, se le redirigirá a Microsoft para la autenticación. Después de iniciar sesión con su
Account de Microsoft (si no lo ha hecho) se le pedirá para permitir que la aplicación acceder a su información:
Pulse Sí y se le redirigirá al sitio web donde puede establecer el correo electrónico.
Ahora que haya iniciado sesión con sus credenciales de Microsoft:
Solución de problemas
Si el proveedor de Microsoft Account le redirige a una página de error de inicio de sesión, tenga en cuenta
el error título y descripción de la cadena parámetros de consulta justo después del # (hashtag) en el Uri.
Aunque parezca que el mensaje de error indica un problema con la autenticación de Microsoft, la causa
más común es la aplicación Uri no coincide con ninguno de los URI de redireccionamiento especificado
para la Web plataforma .
ASP.NET Core solo 2.x: identidad si no está configurado mediante una llamada a services.AddIdentity
en ConfigureServices , intenta autenticar se producirá en ArgumentException: se debe proporcionar la
opción 'SignInScheme'. La plantilla de proyecto que se usan en este tutorial se asegura de que esto se
realiza.
Si la base de datos de sitio no se ha creado mediante la aplicación de la migración inicial, obtendrá error en
una operación de base de datos al procesar la solicitud error. Pulse migraciones aplicar para crear la base
de datos y actualizar para continuar después del error.
Pasos siguientes
En este artículo se ha explicado cómo puede autenticar con Microsoft. Puede seguir un enfoque similar
para autenticar con otros proveedores que se enumeran en la página anterior.
Una vez que se publica un sitio web a la aplicación web de Azure, debe crear un nuevo Password en el
Portal para desarrolladores de Microsoft.
Establecer el Authentication:Microsoft:ApplicationId y Authentication:Microsoft:Password como
configuración de la aplicación en el portal de Azure. El sistema de configuración está configurado para leer
las claves de las variables de entorno.
Breve encuesta de otros proveedores de
autenticación
22/06/2018 • 2 minutes to read • Edit Online
services.AddAuthentication()
.AddMicrosoftAccount(microsoftOptions => { ... })
.AddGoogle(googleOptions => { ... })
.AddTwitter(twitterOptions => { ... })
.AddFacebook(facebookOptions => { ... });
Autenticar a los usuarios con WS-Federation en
ASP.NET Core
22/06/2018 • 7 minutes to read • Edit Online
Este tutorial muestra cómo permitir a los usuarios iniciar sesión con un proveedor de autenticación de WS -
Federation como Active Directory Federation Services (ADFS ) o Azure Active Directory (AAD ). Usa la aplicación
de ejemplo básica de ASP.NET 2.0 se describe en Facebook, Google y la autenticación de proveedor externo.
Para las aplicaciones de ASP.NET Core 2.0, ofrece compatibilidad con WS -Federation
Microsoft.AspNetCore.Authentication.WsFederation. Este componente se procede de
Microsoft.Owin.Security.WsFederation y comparte muchos de los mecanismos de ese componente. Sin embargo,
los componentes se diferencian en un par de aspectos importantes.
De forma predeterminada, el middleware nueva:
No permite que los inicios de sesión no solicitados. Esta característica del protocolo WS -Federation es
vulnerable a ataques XSRF. Sin embargo, se puede habilitar con el AllowUnsolicitedLogins opción.
No se comprueba cada formulario post para los mensajes de inicio de sesión. Solo se solicita a la CallbackPath
se comprueba el inicio de sesión complementos. CallbackPath tiene como valor predeterminado
/signin-wsfed pero puede cambiarse a través de los heredados RemoteAuthenticationOptions.CallbackPath
propiedad de la WsFederationOptions clase. Esta ruta de acceso se puede compartir con otros proveedores de
autenticación habilitando la SkipUnrecognizedRequests opción.
Escriba un nombre para mostrar para el usuario autenticado. El nombre no es importante para la aplicación
de ASP.NET Core.
Microsoft.AspNetCore.Authentication.WsFederation no es compatible con el cifrado de tokens, por lo que
no configure un certificado de cifrado de tokens:
Habilitar la compatibilidad de protocolo WS -Federation Passive, utilizando la dirección URL de la aplicación.
Compruebe que el puerto sea correcto para la aplicación:
NOTE
Debe ser una dirección URL HTTPS. IIS Express puede proporcionar un certificado autofirmado al hospedar la aplicación
durante el desarrollo. Kestrel requiere configuración manual de certificados. Consulte la documentación Kestrel para obtener
más detalles.
Haga clic en siguiente a través del resto del asistente y cerrar al final.
Identidad de núcleo ASP.NET requiere un Id. de nombre de notificación. Agregue uno de los editar reglas
de notificación cuadro de diálogo:
En el transformar notificaciones Asistente para agregar reglas, deje el valor predeterminado enviar
atributos LDAP como notificaciones plantilla seleccionada y haga clic en siguiente. Agregar una asignación
de regla el nombre de cuenta SAM atributo LDAP para la Id. de nombre notificación saliente:
Haga clic en finalizar > Aceptar en el editar reglas de notificación ventana.
Azure Active Directory
Vaya a la hoja de los registros de aplicación del inquilino AAD. Haga clic en nuevo registro de aplicación:
Escriba un nombre para el registro de aplicación. Esto no es importante para la aplicación de ASP.NET Core.
Escriba la dirección URL de la aplicación de escucha en que la dirección URL de inicio de sesión:
Haga clic en extremos y tenga en cuenta el documento de metadatos de federación dirección URL. Se
trata el middleware de WS -Federation MetadataAddress :
Navegue hasta el nuevo registro de aplicación. Haga clic en configuración > propiedades y tome nota de la
App ID URI. Se trata el middleware de WS -Federation Wtrealm :
Agregar WS-Federation como proveedor de inicio de sesión externo
para ASP.NET Core Identity
Agregue una dependencia en Microsoft.AspNetCore.Authentication.WsFederation al proyecto.
Agregar WS -Federation para la Configure método Startup.cs:
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication()
.AddWsFederation(options =>
{
// MetadataAddress represents the Active Directory instance used to authenticate users.
options.MetadataAddress = "https://<ADFS FQDN or AAD tenant>/FederationMetadata/2007-
06/FederationMetadata.xml";
// For AAD, use the App ID URI from the app registration's Properties blade:
options.Wtrealm = "https://wsfedsample.onmicrosoft.com/bf0e7e6d-056e-4e37-b9a6-2c36797b9f01";
});
services.AddMvc()
// ...
Con AD FS como el proveedor, el botón se redirige a una página de inicio de sesión de AD FS:
Con Azure Active Directory como el proveedor, el botón se redirige a una página de inicio de sesión AAD:
Un inicio de sesión correcto en un nuevo usuario redirige a la página de registro de usuario de la aplicación:
Requisitos previos
Install one of the following:
CLI tooling: Windows, Linux, or macOS: .NET Core SDK 2.0 or later
IDE/editor tooling
Windows: Visual Studio for Windows
ASP.NET and web development workload
.NET Core cross-platform development workload
Linux: Visual Studio Code
macOS: Visual Studio for Mac
NOTE
In ASP.NET Core 2.1 or later, webapp is an alias of the razor argument. If the dotnet new webapp <OPTIONS> command
loads the dotnet new command help instead of creating a new Razor Pages app, install the .NET Core 2.1 SDK.
Requerir HTTPS
Vea requieren HTTPS.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizeFolder("/Account/Manage");
options.Conventions.AuthorizePage("/Account/Logout");
});
config.SignIn.RequireConfirmedEmail = true; impide que los usuarios registrados el registro hasta que se ha
confirmado el correo electrónico.
Configurar el proveedor de correo electrónico
En este tutorial, se usa SendGrid para enviar correo electrónico. Necesita una cuenta de SendGrid y la clave para
enviar correo electrónico. Puede usar otros proveedores de correo electrónico. ASP.NET Core 2.x incluye
System.Net.Mail , que le permite enviar correo electrónico desde la aplicación. Se recomienda que usar SendGrid
u otro servicio de correo electrónico para enviar correo electrónico. SMTP es difícil proteger y configurado
correctamente.
El patrón opciones se usa para acceder a la configuración de cuenta y clave de usuario. Para obtener más
información, consulte configuración.
Cree una clase para obtener la clave de proteger el correo electrónico. En este ejemplo, el
AuthMessageSenderOptions se crea una clase en el Services/AuthMessageSenderOptions.cs archivo:
El contenido de la secrets.json archivos no están cifrados. El secrets.json archivo se muestra a continuación (el
SendGridKey se ha quitado el valor.)
{
"SendGridUser": "RickAndMSFT",
"SendGridKey": "<key removed>"
}
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizeFolder("/Account/Manage");
options.Conventions.AuthorizePage("/Account/Logout");
});
services.AddSingleton<IEmailSender, EmailSender>();
services.Configure<AuthMessageSenderOptions>(Configuration);
}
using Microsoft.Extensions.Options;
using SendGrid;
using SendGrid.Helpers.Mail;
using System.Threading.Tasks;
namespace WebPWrecover.Services
{
public class EmailSender : IEmailSender
{
public EmailSender(IOptions<AuthMessageSenderOptions> optionsAccessor)
{
Options = optionsAccessor.Value;
}
public Task Execute(string apiKey, string subject, string message, string email)
{
var client = new SendGridClient(apiKey);
var msg = new SendGridMessage()
{
From = new EmailAddress("[email protected]", "Joe Smith"),
Subject = subject,
PlainTextContent = message,
HtmlContent = message
};
msg.AddTo(new EmailAddress(email));
return client.SendEmailAsync(msg);
}
}
}
Haga clic en el administrar vínculo. Tenga en cuenta el externo 0 (inicios de sesión sociales) asociados con esta
cuenta.
Haga clic en el vínculo a otro servicio de inicio de sesión y Aceptar las solicitudes de aplicación. En la siguiente
imagen, Facebook es el proveedor de autenticación externos:
Se han combinado las dos cuentas. Es posible iniciar sesión con cualquiera de estas cuentas. Puede que los
usuarios agreguen cuentas locales en caso de que su servicio de autenticación de inicio de sesión social está
inactivo o, más probablemente ha perdido acceso a su cuenta sociales.
@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
}
Actualización de la Scripts sección para agregar una referencia a la qrcodejs biblioteca que agregó y una
llamada a generar el código QR. Debería ser como sigue:
@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
Agregue el paquete de NuGet del proveedor de SMS. Desde el paquete de administrador de consola (PMC )
ejecutar:
Twilio:
Install-Package Twilio
ASPSMS:
Install-Package ASPSMS
Agregue código en el Services/MessageServices.cs archivo para habilitar SMS. Utilice la Twilio o la sección
ASPSMS:
Twilio:
using Microsoft.Extensions.Options;
using System.Threading.Tasks;
using Twilio;
using Twilio.Rest.Api.V2010.Account;
using Twilio.Types;
namespace Web2FA.Services
{
// This class is used by the application to send Email and SMS
// when you turn on two-factor authentication in ASP.NET Identity.
// For more details see this link https://go.microsoft.com/fwlink/?LinkID=532713
public class AuthMessageSender : IEmailSender, ISmsSender
{
public AuthMessageSender(IOptions<SMSoptions> optionsAccessor)
{
Options = optionsAccessor.Value;
}
TwilioClient.Init(accountSid, authToken);
return MessageResource.CreateAsync(
to: new PhoneNumber(number),
from: new PhoneNumber(Options.SMSAccountFrom),
body: message);
}
}
}
ASPSMS:
using Microsoft.Extensions.Options;
using System.Threading.Tasks;
namespace Web2FA.Services
{
// This class is used by the application to send Email and SMS
// when you turn on two-factor authentication in ASP.NET Identity.
// For more details see this link https://go.microsoft.com/fwlink/?LinkID=532713
public class AuthMessageSender : IEmailSender, ISmsSender
{
public AuthMessageSender(IOptions<SMSoptions> optionsAccessor)
{
Options = optionsAccessor.Value;
}
SMSSender.Userkey = Options.SMSAccountIdentification;
SMSSender.Password = Options.SMSAccountPassword;
SMSSender.Originator = Options.SMSAccountFrom;
SMSSender.AddRecipient(number);
SMSSender.MessageData = message;
SMSSender.SendTextSMS();
return Task.FromResult(0);
}
}
}
Agregar un número de teléfono que recibirá el código de comprobación y pulse enviar código de
comprobación.
Obtendrá un mensaje de texto con el código de comprobación. Escríbala y pulse enviar
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
services.Configure<IdentityOptions>(options =>
{
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10);
});
Configuración
ASP.NET Core 2.x
ASP.NET Core 1.x
Si la aplicación no usa el Microsoft.AspNetCore.App metapackage, cree una referencia de paquete en el archivo
de proyecto para la Microsoft.AspNetCore.Authentication.Cookies paquete (versión 2.1.0 o más adelante).
En el ConfigureServices (método), crear el servicio de Middleware de autenticación con el AddAuthentication y
AddCookie métodos:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie();
app.UseAuthentication();
Opciones de AddCookie
El CookieAuthenticationOptions clase se utiliza para configurar las opciones de proveedor de autenticación.
OPCIÓN DESCRIPCIÓN
Cookie.HttpOnly Una marca que indica si la cookie debe ser accesible sólo a los
servidores. Si cambia este valor a false permite que los
scripts del lado cliente para tener acceso a la cookie y se
puede abrir la aplicación al robo de cookies debe tener la
aplicación un scripting entre sitios (XSS) una vulnerabilidad. El
valor predeterminado es true .
slidingExpiration Una marca que indica si se debe emitir una nueva cookie con
una fecha de caducidad actualizada dinámicamente. Esto
puede suceder en cualquier solicitud que el período de
expiración de cookie actual es más del 50% expira. La nueva
fecha de expiración se mueve hacia delante como la fecha
actual más el ExpireTimespan . Un tiempo de expiración de
cookie absoluta puede establecerse mediante el
AuthenticationProperties al llamar a la clase
SignInAsync . Una hora de expiración absoluta puede
mejorar la seguridad de la aplicación mediante la limitación de
la cantidad de tiempo que la cookie de autenticación es válida.
El valor predeterminado es true .
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
...
});
app.UseCookiePolicy(cookiePolicyOptions);
PROPERTY DESCRIPCIÓN
CONFIGURACIÓN DE COOKIE.SAMESITE
MINIMUMSAMESITEPOLICY COOKIE.SAMESITE RESULTANTE
//ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(10),
// The time at which the authentication ticket expires. A
// value set here overrides the ExpireTimeSpan option of
// CookieAuthenticationOptions set with AddCookie.
//IsPersistent = true,
// Whether the authentication session is persisted across
// multiple requests. Required when setting the
// ExpireTimeSpan option of CookieAuthenticationOptions
// set with AddCookie. Also required when setting
// ExpiresUtc.
//IssuedUtc = <DateTimeOffset>,
// The time at which the authentication ticket was issued.
//RedirectUri = <string>
// The full path or absolute URI to be used as an http
// redirect response value.
};
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
authProperties);
SignInAsync crea una cookie cifrada y lo agrega a la respuesta actual. Si no se especifica un AuthenticationScheme
, se utiliza el esquema predeterminado.
Tras los bastidores, el cifrado usado es ASP.NET Core protección de datos sistema. Si aloja la aplicación en varios
equipos, equilibrio de carga entre las aplicaciones o usar una granja de servidores web, debe configurar la
protección de datos para usar el mismo anillo de clave y el identificador de la aplicación.
Cerrar sesión
ASP.NET Core 2.x
ASP.NET Core 1.x
Para cerrar la sesión del usuario actual y eliminar las cookies, llame a SignOutAsync:
await HttpContext.SignOutAsync(
CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity));
ValidatePrincipal(CookieValidatePrincipalContext)
if (string.IsNullOrEmpty(lastChanged) ||
!_userRepository.ValidateLastChanged(lastChanged))
{
context.RejectPrincipal();
await context.HttpContext.SignOutAsync(
CookieAuthenticationDefaults.AuthenticationScheme);
}
}
}
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.EventsType = typeof(CustomCookieAuthenticationEvents);
});
services.AddScoped<CustomCookieAuthenticationEvents>();
Considere la posibilidad de una situación en la que se actualiza el nombre del usuario — una decisión que no
afectan a la seguridad de ninguna manera. Si desea actualizar la entidad de seguridad de usuario de manera no
destructiva, llame a context.ReplacePrincipal y establezca el context.ShouldRenew propiedad true .
WARNING
El enfoque descrito aquí se desencadena en cada solicitud. Esto puede dar lugar a una reducción del rendimiento de gran
tamaño de la aplicación.
Cookies persistentes
Puede que desee la cookie que se conservan entre sesiones del explorador. Esta persistencia solo debería
habilitarse con el consentimiento del usuario explícita con una casilla "Recordar mi cuenta" en Inicio de sesión o
un mecanismo similar.
El fragmento de código siguiente crea una identidad y cookie correspondiente que sobrevive a través de los
cierres de explorador. Se respetan los parámetros de expiración deslizante configurados previamente. Si la cookie
expira mientras se cierra el explorador, el explorador borra la cookie de una vez que se reinicie.
ASP.NET Core 2.x
ASP.NET Core 1.x
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
new AuthenticationProperties
{
IsPersistent = true
});
El fragmento de código siguiente crea una identidad y cookie correspondiente que tiene una validez de 20
minutos. Esto omite cualquier configuración de expiración deslizante configurado previamente.
ASP.NET Core 2.x
ASP.NET Core 1.x
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTime.UtcNow.AddMinutes(20)
});
Recursos adicionales
Cambios de autenticación 2.0 / anuncio de migración
Limitación de la identidad por esquema
Autorización basada en notificaciones
Comprobaciones de la función basada en directivas
Azure Active Directory con ASP.NET Core
21/06/2018 • 2 minutes to read • Edit Online
Ejemplos de Azure AD V1
En los ejemplos siguientes se muestra cómo integrar Azure AD V1, lo que permite a los usuarios iniciar sesión con
una cuenta profesional o educativa:
Integración de Azure AD en una aplicación web de ASP.NET Core
Llamada a una API Web de ASP.NET Core desde una aplicación de WPF con Azure AD
Llamada a una API Web en una aplicación web de ASP.NET Core con Azure AD
Ejemplos de Azure AD V2
En los ejemplos siguientes se muestra cómo integrar Azure AD V2, lo que permite a los usuarios iniciar sesión con
una cuenta profesional o educativa o con una cuenta de Microsoft personal (anteriormente, cuenta de Live):
Integración de Azure AD V2 en una aplicación web ASP.NET Core 2.0:
Vea este vídeo asociado.
Llamada a una API web ASP.NET Core 2.0 desde una aplicación de WPF con Azure AD V2:
Vea este vídeo asociado.
TIP
Azure Active Directory (Azure AD) Azure AD B2C son ofertas de productos independientes. Un inquilino de Azure AD
representa una organización, mientras que un inquilino de Azure AD B2C representa una colección de identidades para su
uso con aplicaciones de usuario de confianza. Para obtener más información, consulte Azure AD B2C: preguntas más
frecuentes (P+F).
Requisitos previos
Se requiere para este tutorial lo siguiente:
Suscripción de Microsoft Azure
Visual Studio de 2017 (cualquier edición)
URI del Id. de aplicación Deje en blanco No se necesita para este tutorial.
WARNING
Si la configuración de una URL de respuesta no localhost, tener en cuenta el restricciones en lo que se permite en la lista
dirección URL de respuesta.
Una vez registrada la aplicación, se muestra la lista de aplicaciones en el inquilino. Seleccione la aplicación que se
acaba de registrar. Seleccione el copia icono a la derecha de la Id. de aplicación campo para copiarlo en el
Portapapeles.
No hay nada más pueden configurarse en el inquilino de Azure AD B2C en este momento, pero deje abierta la
ventana del explorador. Hay una mayor configuración después de crea la aplicación de ASP.NET Core.
PARÁMETRO VALOR
Seleccione el copia vínculo junto a URI de respuesta para copiar el URI de respuesta en el Portapapeles.
Seleccione Aceptar para cerrar el Cambiar autenticación cuadro de diálogo. Seleccione Aceptar para
crear la aplicación web.
TIP
Si no copia la dirección URL de respuesta, use la dirección SSL desde la pestaña de depuración en las propiedades del
proyecto web y anexar la CallbackPath valor de appSettings.JSON que se.
Configurar directivas
Siga los pasos de la documentación de Azure AD B2C a crear una directiva de inicio de sesión o inicio de sesióny,
a continuación, crear una directiva de restablecimiento de contraseña. Use los valores de ejemplo proporcionados
en la documentación de proveedores de identidades, atributos suscripción, y notificaciones de la
aplicación. Mediante el ejecutar ahora botón para probar las directivas, como se describe en la documentación
es opcional.
WARNING
Asegúrese de que los nombres de directiva son exactamente como se describe en la documentación de esas directivas se
usaron en el Cambiar autenticación cuadro de diálogo de Visual Studio. Los nombres de directiva se pueden comprobar en
appSettings.JSON que se.
Ejecutar la aplicación
En Visual Studio, presione F5 para compilar y ejecutar la aplicación. Después de que se inicie la aplicación web,
seleccione iniciar sesión en.
El explorador se redirige a los inquilinos de Azure AD B2C. Inicie sesión con una cuenta existente (si se ha creado
uno probar las directivas) o seleccione Regístrese ahora para crear una nueva cuenta. El ¿olvidó su contraseña?
vínculo se usa para restablecer una contraseña olvidada.
Después de iniciar sesión correctamente, el explorador se redirige a la aplicación web.
Pasos siguientes
En este tutorial ha aprendido a:
Crear a un inquilino de Azure Active Directory B2C
Registrar una aplicación en Azure AD B2C
Usar Visual Studio para crear una aplicación de ASP.NET Core Web configurado para usar al inquilino de
Azure AD B2C para la autenticación
Configurar directivas que controlan el comportamiento del inquilino de Azure AD B2C
Ahora que la aplicación de ASP.NET Core está configurada para usar Azure AD B2C para la autenticación, el
atributo Authorize puede usarse para proteger la aplicación. Continuar desarrollando la aplicación por el
aprendizaje para:
Personalizar la interfaz de usuario de Azure AD B2C.
Configurar requisitos de complejidad de contraseña.
Habilitar la autenticación multifactor.
Configurar proveedores de identidades adicional, como Microsoft, Facebook, Google, Amazon, de Twitter y
otros.
Usar la API de Azure AD Graph para recuperar información de usuario adicional, como la pertenencia a
grupos, desde el inquilino de Azure AD B2C.
Proteger un núcleo de ASP.NET web API con Azure AD B2C.
Llamar a una API web de .NET desde una aplicación web de .NET con Azure AD B2C.
Autenticación de nube en web API con Azure Active
Directory B2C en ASP.NET Core
23/06/2018 • 16 minutes to read • Edit Online
TIP
Azure Active Directory (Azure AD) y Azure AD B2C son ofertas de producto independiente. Un inquilino de Azure AD
representa una organización, mientras que un inquilino de Azure AD B2C representa una colección de identidades para su
uso con aplicaciones de usuario de confianza. Para obtener más información, consulte Azure AD B2C: preguntas más
frecuentes (P+F).
Puesto que las API web no tienen ninguna interfaz de usuario, se trata de no se puede redirigir al usuario a un
servicio de token seguro como Azure AD B2C. En su lugar, la API se pasa un token de portador de la aplicación
que realiza la llamada, que ya se ha autenticado al usuario con Azure AD B2C. La API, a continuación, valida el
token sin interacción directa del usuario.
En este tutorial, aprenderá cómo:
Crear a un inquilino de Azure Active Directory B2C.
Registrar una API Web en Azure AD B2C.
Usar Visual Studio para crear una API Web configurado para usar al inquilino de Azure AD B2C para la
autenticación.
Configurar directivas que controlan el comportamiento del inquilino de Azure AD B2C.
Use Postman para simular una aplicación web que presenta un cuadro de diálogo de inicio de sesión, recupera
un token y lo utiliza para realizar una solicitud en la API web.
Requisitos previos
Se requiere para este tutorial lo siguiente:
Suscripción de Microsoft Azure
Visual Studio de 2017 (cualquier edición)
Postman
URI del Id. de aplicación API El URI no tiene que resolverse en una
dirección física. Solo debe ser único.
Una vez registrada la API, se muestra la lista de aplicaciones y las API en el inquilino. Seleccione la API que se
acaba de registrar. Seleccione el copia icono a la derecha de la Id. de aplicación campo para copiarlo en el
Portapapeles. Seleccione publicado ámbitos y compruebe el valor predeterminado user_impersonation ámbito
está presente.
PARÁMETRO VALOR
Seleccione Aceptar para cerrar el Cambiar autenticación cuadro de diálogo. Seleccione Aceptar para
crear la aplicación web.
Visual Studio crea la API web con un controlador denominado ValuesController.cs que devuelva valores
codificados de forma rígida para las solicitudes GET. La clase se decora con el atributo Authorize, por lo que todas
las solicitudes requieren autenticación.
NOTE
Puesto que no hay ningún controlador definido para la dirección URL raíz, el explorador muestra un error 404 de (página no
encontrada). Este es el comportamiento normal.
Name Postman
URI del Id. de aplicación <Deje en blanco> No se necesita para este tutorial.
La aplicación web recién registrado necesita permiso para tener acceso a la API web en nombre del usuario.
1. Seleccione Postman en la lista de aplicaciones y, a continuación, seleccione el acceso de API en el menú de la
izquierda.
2. Seleccione + agregar.
3. En el API seleccione de lista desplegable, seleccione el nombre de la API web.
4. En el seleccionar ámbitos de lista desplegable, asegúrese de que se seleccionan todos los ámbitos.
5. Seleccione Aceptar.
Tenga en cuenta Id. la aplicación Postman de aplicación, tal y como lo necesario para obtener un token de
portador.
Crear una solicitud de Postman
Inicie a Postman. De forma predeterminada, se muestra Postman el crear nuevo tras iniciar el cuadro de diálogo.
Si no se muestra el cuadro de diálogo, seleccione la + nuevo botón en la parte superior izquierda.
Desde el crear nuevo cuadro de diálogo:
1. Seleccione solicitar.
Nombre del token <nombre del token> Escriba un nombre descriptivo para el
token.
Identidad de ASP.NET Core se incluye en las plantillas de proyecto en Visual Studio con la opción de "Cuentas de
usuario individuales".
Las plantillas de autenticación están disponibles en .NET Core CLI con -au Individual :
NOTE
In ASP.NET Core 2.1 or later, webapp is an alias of the razor argument. If the dotnet new webapp <OPTIONS> command
loads the dotnet new command help instead of creating a new Razor Pages app, install the .NET Core 2.1 SDK.
Los artículos siguientes muestran cómo usar el código generado en las plantillas de ASP.NET Core que utilizan
cuentas de usuario individuales:
Autenticación en dos fases con SMS
Confirmación de las cuentas y recuperación de contraseñas en ASP.NET Core
Crear una aplicación de ASP.NET Core con datos de usuario protegidos por autorización
Autorización en ASP.NET Core
19/06/2018 • 2 minutes to read • Edit Online
Introducción
Creación de una aplicación con datos de usuario protegidos por autorización
Autorización de páginas de Razor
Autorización simple
Autorización basada en roles
Autorización basada en notificaciones
Autorización basada en directivas
Proveedores de directivas de autorización personalizada
Inserción de dependencias en controladores de requisitos
Autorización basada en recursos
Autorización basada en visualizaciones
Authorize with a specific scheme (Autorización con un esquema específico)
Introducción a la autorización en ASP.NET Core
22/06/2018 • 2 minutes to read • Edit Online
La autorización se refiere al proceso que determina lo que un usuario es capaz de hacer. Por ejemplo, un usuario
administrativo puede crear una biblioteca de documentos, agregar documentos, editar documentos y eliminarlos.
Un usuario sin derechos administrativos que trabaje con la biblioteca solo está autorizado a leer los documentos.
La autorización es ortogonal y es independiente de la autenticación. Sin embargo, la autorización requiere un
mecanismo de autenticación. La autenticación es el proceso de determinar quién es un usuario. La autenticación
puede crear una o varias identidades para el usuario actual.
Tipos de autorización
Autorización de ASP.NET Core proporcionan una forma simple, declarativa rol y un variado basada en directivas
modelo. Autorización se expresa en los requisitos y controladores evaluación notificaciones de usuario con los
requisitos. Las comprobaciones imperativas pueden basarse en directivas simples ni que evaluar la identidad del
usuario y propiedades del recurso al que el usuario está intentando tener acceso.
Espacios de nombres
Componentes de autorización, incluido el AuthorizeAttribute y AllowAnonymousAttribute atributos, se encuentran
en el Microsoft.AspNetCore.Authorization espacio de nombres.
Consulte la documentación en autorización sencilla.
Crear una aplicación de ASP.NET Core con datos de
usuario protegidos por autorización
22/06/2018 • 29 minutes to read • Edit Online
El aprobar y rechazar solo se muestran los botones para los administradores y administradores.
En la siguiente imagen, [email protected] está firmado en y en la función Administradores:
El administrador tiene todos los privilegios. Puede leer, editar o eliminar cualquier contacto y cambiar el estado de
contactos.
La aplicación se creó con scaffolding siguiente Contact modelo:
Requisitos previos
Este tutorial se avanza. Debe estar familiarizado con:
ASP.NET Core
Autenticación
Confirmación de cuentas y recuperación de contraseñas
Autorización
Entity Framework Core
Vea este archivo PDF para la versión principal de ASP.NET MVC. La versión 1.1 de ASP.NET Core de este tutorial
está en esto carpeta. La 1.1 incluye el ejemplo de ASP.NET Core el ejemplos.
OwnerID es el identificador del usuario desde el AspNetUser tabla el identidad base de datos. El Status campo
determina si un contacto es visible para los usuarios en general.
Crear una nueva migración y actualizar la base de datos:
dotnet ef migrations add userID_Status
dotnet ef database update
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
Con el requisito de todos los usuarios autenticados, el AuthorizeFolder y AuthorizePage llamadas no son
necesarias.
Actualización ConfigureServices con los cambios siguientes:
Convertir en comentario AuthorizeFolder y AuthorizePage .
Establecer la directiva de autenticación predeterminado para exigir que los usuarios se autentiquen.
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
//.AddRazorPagesOptions(options =>
//{
// options.Conventions.AuthorizeFolder("/Account/Manage");
// options.Conventions.AuthorizePage("/Account/Logout");
//});
services.AddSingleton<IEmailSender, EmailSender>();
Agregar AllowAnonymous al índice, las páginas sobre y, a continuación, póngase en contacto con por lo que los
usuarios anónimos pueden recibir información sobre el sitio antes de que registrar.
}
}
Si no utiliza una contraseña segura, se produce una excepción cuando SeedData.Initialize se llama.
Actualización Main usar la contraseña de prueba:
try
{
SeedData.Initialize(services, testUserPw).Wait();
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while seeding the database.");
throw ex;
}
}
host.Run();
}
// allowed user can create and edit contacts that they create
var uid = await EnsureUser(serviceProvider, testUserPw, "[email protected]");
await EnsureRole(serviceProvider, uid, Constants.ContactManagersRole);
SeedDB(context, adminID);
}
}
return user.Id;
}
if (!await roleManager.RoleExistsAsync(role))
{
IR = await roleManager.CreateAsync(new IdentityRole(role));
}
return IR;
}
Agregue el identificador de usuario de administrador y ContactStatus a los contactos. Realice uno de los
contactos "Enviado" y un "rechazada". Agregue el Id. de usuario y el estado para todos los contactos. Póngase en
contacto un solo con se muestra:
public static void SeedDB(ApplicationDbContext context, string adminID)
{
if (context.Contact.Any())
{
return; // DB has been seeded
}
context.Contact.AddRange(
new Contact
{
Name = "Debra Garcia",
Address = "1234 Main St",
City = "Redmond",
State = "WA",
Zip = "10999",
Email = "[email protected]",
Status = ContactStatus.Approved,
OwnerID = adminID
},
namespace ContactManager.Authorization
{
public class ContactIsOwnerAuthorizationHandler
: AuthorizationHandler<OperationAuthorizationRequirement, Contact>
{
UserManager<ApplicationUser> _userManager;
public ContactIsOwnerAuthorizationHandler(UserManager<ApplicationUser>
userManager)
{
_userManager = userManager;
}
if (resource.OwnerID == _userManager.GetUserId(context.User))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
}
using System.Threading.Tasks;
using ContactManager.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;
using Microsoft.AspNetCore.Identity;
namespace ContactManager.Authorization
{
public class ContactManagerAuthorizationHandler :
AuthorizationHandler<OperationAuthorizationRequirement, Contact>
{
protected override Task
HandleRequirementAsync(AuthorizationHandlerContext context,
OperationAuthorizationRequirement requirement,
Contact resource)
{
if (context.User == null || resource == null)
{
return Task.CompletedTask;
}
return Task.CompletedTask;
}
}
}
namespace ContactManager.Authorization
{
public class ContactAdministratorsAuthorizationHandler
: AuthorizationHandler<OperationAuthorizationRequirement, Contact>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
OperationAuthorizationRequirement requirement,
Contact resource)
{
if (context.User == null)
{
return Task.CompletedTask;
}
return Task.CompletedTask;
}
}
}
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
//.AddRazorPagesOptions(options =>
//{
// options.Conventions.AuthorizeFolder("/Account/Manage");
// options.Conventions.AuthorizePage("/Account/Logout");
//});
services.AddSingleton<IEmailSender, EmailSender>();
// Authorization handlers.
services.AddScoped<IAuthorizationHandler,
ContactIsOwnerAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler,
ContactAdministratorsAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler,
ContactManagerAuthorizationHandler>();
}
Admitir la autorización
En esta sección, actualice las páginas Razor y agregar una clase de requisitos de las operaciones.
Revisión de la clase de requisitos de operaciones de contacto
Revise la ContactOperations clase. Esta clase contiene los requisitos de la aplicación admite:
using Microsoft.AspNetCore.Authorization.Infrastructure;
namespace ContactManager.Authorization
{
public static class ContactOperations
{
public static OperationAuthorizationRequirement Create =
new OperationAuthorizationRequirement {Name=Constants.CreateOperationName};
public static OperationAuthorizationRequirement Read =
new OperationAuthorizationRequirement {Name=Constants.ReadOperationName};
public static OperationAuthorizationRequirement Update =
new OperationAuthorizationRequirement {Name=Constants.UpdateOperationName};
public static OperationAuthorizationRequirement Delete =
new OperationAuthorizationRequirement {Name=Constants.DeleteOperationName};
public static OperationAuthorizationRequirement Approve =
new OperationAuthorizationRequirement {Name=Constants.ApproveOperationName};
public static OperationAuthorizationRequirement Reject =
new OperationAuthorizationRequirement {Name=Constants.RejectOperationName};
}
using ContactManager.Data;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace ContactManager.Pages.Contacts
{
public class DI_BasePageModel : PageModel
{
protected ApplicationDbContext Context { get; }
protected IAuthorizationService AuthorizationService { get; }
protected UserManager<ApplicationUser> UserManager { get; }
public DI_BasePageModel(
ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<ApplicationUser> userManager) : base()
{
Context = context;
UserManager = userManager;
AuthorizationService = authorizationService;
}
}
}
El código anterior:
Agrega el IAuthorizationService servicio para tener acceso a los controladores de autorización.
Agrega la identidad UserManager servicio.
Agregue la ApplicationDbContext .
Actualizar el CreateModel
Actualizar el constructor de modelo de página de create para usar la DI_BasePageModel clase base:
Contact.OwnerID = UserManager.GetUserId(User);
Context.Contact.Add(Contact);
await Context.SaveChangesAsync();
return RedirectToPage("./Index");
}
Actualizar la IndexModel
Actualización de la OnGetAsync método por lo que sólo los contactos aprobados se muestran a los usuarios en
general:
public class IndexModel : DI_BasePageModel
{
public IndexModel(
ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<ApplicationUser> userManager)
: base(context, authorizationService, userManager)
{
}
// Only approved contacts are shown UNLESS you're authorized to see them
// or you are the owner.
if (!isAuthorized)
{
contacts = contacts.Where(c => c.Status == ContactStatus.Approved
|| c.OwnerID == currentUserId);
}
Actualizar la EditModel
Agregue un controlador de autorización para comprobar que el usuario es propietario del contacto. Dado que se
está validando la autorización de recursos, el [Authorize] atributo no es suficiente. La aplicación no tiene acceso
al recurso cuando se evalúan los atributos. Autorización basada en recursos debe ser imperativo. Las
comprobaciones deben realizarse una vez que la aplicación tenga acceso al recurso, cargándolo en el modelo de
páginas o cargándolo en su propio controlador. Con frecuencia tener acceso al recurso pasando la clave de
recurso.
[BindProperty]
public Contact Contact { get; set; }
if (Contact == null)
{
return NotFound();
}
}
return Page();
}
if (contact == null)
{
return NotFound();
}
Contact.OwnerID = contact.OwnerID;
Context.Attach(Contact).State = EntityState.Modified;
if (contact.Status == ContactStatus.Approved)
{
// If the contact is updated after approval,
// and the user cannot approve,
// set the status back to submitted so the update can be
// checked and approved.
var canApprove = await AuthorizationService.AuthorizeAsync(User,
contact,
ContactOperations.Approve);
if (!canApprove.Succeeded)
{
contact.Status = ContactStatus.Submitted;
}
}
await Context.SaveChangesAsync();
return RedirectToPage("./Index");
}
[BindProperty]
public Contact Contact { get; set; }
if (Contact == null)
{
return NotFound();
}
return Page();
}
if (contact == null)
{
return NotFound();
}
Context.Contact.Remove(Contact);
await Context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
Insertar el servicio de autorización en las vistas
Actualmente, se muestra en la interfaz de usuario edita y elimina los vínculos de datos que no se puede modificar
el usuario. La interfaz de usuario es fijo aplicando el controlador de autorización a las vistas.
Insertar el servicio de autorización en el Views/_ViewImports.cshtml para que esté disponible para todas las vistas
de archivos:
@using Microsoft.AspNetCore.Identity
@using ContactManager
@using ContactManager.Data
@namespace ContactManager.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@using ContactManager.Authorization;
@using Microsoft.AspNetCore.Authorization
@using ContactManager.Models
@inject IAuthorizationService AuthorizationService
@page
@model ContactManager.Pages.Contacts.IndexModel
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Contact[0].Name)
</th>
<th>
@Html.DisplayNameFor(model => model.Contact[0].Address)
</th>
<th>
@Html.DisplayNameFor(model => model.Contact[0].City)
</th>
<th>
@Html.DisplayNameFor(model => model.Contact[0].State)
</th>
<th>
@Html.DisplayNameFor(model => model.Contact[0].Zip)
</th>
<th>
@Html.DisplayNameFor(model => model.Contact[0].Email)
</th>
<th>
@Html.DisplayNameFor(model => model.Contact[0].Status)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Contact)
{
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Address)
</td>
<td>
@Html.DisplayFor(modelItem => item.City)
</td>
<td>
@Html.DisplayFor(modelItem => item.State)
</td>
<td>
@Html.DisplayFor(modelItem => item.Zip)
</td>
<td>
@Html.DisplayFor(modelItem => item.Email)
</td>
<td>
@Html.DisplayFor(modelItem => item.Status)
</td>
<td>
@if ((await AuthorizationService.AuthorizeAsync(
User, item,
ContactOperations.Update)).Succeeded)
{
<a asp-page="./Edit" asp-route-id="@item.ContactId">Edit</a>
<text> | </text>
}
WARNING
Ocultar los vínculos de los usuarios que no tienen permiso para cambiar los datos no proteger la aplicación. Ocultar los
vínculos hace que la aplicación más fácil de usar mostrando vínculos solo es válido. Los usuarios pueden hack las direcciones
URL generadas para invocar Editar y eliminar operaciones en los datos que no poseen. La página de Razor o el controlador
debe exigir comprobaciones de acceso para proteger los datos.
Detalles de la actualización
Actualice la vista de detalles para que los administradores pueden aprobar o rechazar contactos:
@*Precedng markup omitted for brevity.*@
<dd>
@Html.DisplayFor(model => model.Contact.Email)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Contact.Status)
</dt>
<dd>
@Html.DisplayFor(model => model.Contact.Status)
</dd>
</dl>
</div>
<div>
@if ((await AuthorizationService.AuthorizeAsync(
User, Model.Contact,
ContactOperations.Update)).Succeeded)
{
<a asp-page="./Edit" asp-route-id="@Model.Contact.ContactId">Edit</a>
<text> | </text>
}
<a asp-page="./Index">Back to List</a>
</div>
if (Contact == null)
{
return NotFound();
}
return Page();
}
if (contact == null)
{
return NotFound();
}
return RedirectToPage("./Index");
}
}
USUARIO OPCIONES
Crear un contacto en el explorador del administrador. Copie la dirección URL para eliminar y editar en el contacto
del administrador. Pegue estos vínculos en el explorador del usuario de prueba para comprobar que el usuario de
prueba no puede realizar estas operaciones.
NOTE
In ASP.NET Core 2.1 or later, webapp is an alias of the razor argument. If the dotnet new webapp <OPTIONS> command
loads the dotnet new command help instead of creating a new Razor Pages app, install the .NET Core 2.1 SDK.
try
{
SeedData.Initialize(services, "").Wait();
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while seeding the database.");
throw ex;
}
}
host.Run();
}
Compruebe que la aplicación había propagado la base de datos. Si no hay ninguna fila en la base de datos de
contacto, no se ejecuta el método de inicialización.
Recursos adicionales
Laboratorio de autorización de ASP.NET Core. Este laboratorio se explica con más detalle en las características
de seguridad introducidas en este tutorial.
Autorización de ASP.NET Core: Simple, rol, basada en notificaciones y personalizado
Autorización personalizada basada en directivas
Convenciones de autorización de páginas de Razor
en ASP.NET Core
22/06/2018 • 5 minutes to read • Edit Online
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizePage("/Contact");
options.Conventions.AuthorizeFolder("/Private");
options.Conventions.AllowAnonymousToPage("/Private/PublicPage");
options.Conventions.AllowAnonymousToFolder("/Private/PublicPages");
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
La ruta de acceso especificada es la ruta de acceso del motor de vista, que es la ruta de acceso relativa de las
páginas de Razor raíz sin una extensión y que contiene solo barras diagonales.
Un AuthorizePage sobrecarga está disponible si tiene que especificar una directiva de autorización.
NOTE
Un AuthorizeFilter se puede aplicar a una clase de modelo de página con el [Authorize] atributo de filtro. Para
obtener más información, consulte atributo de filtro de autorizar.
La ruta de acceso especificada es la ruta de acceso del motor de vista, que es la ruta de acceso relativa de las
páginas de Razor raíz.
Un AuthorizeFolder sobrecarga está disponible si tiene que especificar una directiva de autorización.
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizePage("/Contact");
options.Conventions.AuthorizeFolder("/Private");
options.Conventions.AllowAnonymousToPage("/Private/PublicPage");
options.Conventions.AllowAnonymousToFolder("/Private/PublicPages");
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
La ruta de acceso especificada es la ruta de acceso del motor de vista, que es la ruta de acceso relativa de las
páginas de Razor raíz sin una extensión y que contiene solo barras diagonales.
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizePage("/Contact");
options.Conventions.AuthorizeFolder("/Private");
options.Conventions.AllowAnonymousToPage("/Private/PublicPage");
options.Conventions.AllowAnonymousToFolder("/Private/PublicPages");
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
La ruta de acceso especificada es la ruta de acceso del motor de vista, que es la ruta de acceso relativa de las
páginas de Razor raíz.
Sin embargo, lo contrario, no es cierto. No se puede declarar una carpeta de páginas para el acceso anónimo y
especificar una página dentro de autorización:
Que requieren la autorización en la página privada no funcionará porque cuando tanto el AllowAnonymousFilter y
AuthorizeFilter filtros se aplican a la página, el AllowAnonymousFilter wins y controla el acceso.
Recursos adicionales
Proveedores personalizados de rutas y modelos de página de páginas de Razor
PageConventionCollection (clase)
Simple de autorización en ASP.NET Core
22/06/2018 • 2 minutes to read • Edit Online
[Authorize]
public class AccountController : Controller
{
public ActionResult Login()
{
}
Si desea aplicar la autorización a una acción en lugar de con el controlador, se aplican los AuthorizeAttribute
atribuir a la propia acción:
[Authorize]
public ActionResult Logout()
{
}
}
Ahora sólo los usuarios autenticados pueden tener acceso a la Logout (función).
También puede usar el AllowAnonymous atributo para permitir el acceso a los usuarios no autenticados para
acciones individuales. Por ejemplo:
[Authorize]
public class AccountController : Controller
{
[AllowAnonymous]
public ActionResult Login()
{
}
Esto le permitiría solo los usuarios autenticados para la AccountController , excepto para el Login acción, que
es accesible para todos los usuarios, independientemente de su estado autenticado o no autenticado / anónimo.
WARNING
[AllowAnonymous] omite todas las instrucciones de autorización. Si aplica la combinación [AllowAnonymous] y
cualquier [Authorize] siempre se omitirá el atributo, a continuación, los atributos de autorizar. Por ejemplo, si aplica
[AllowAnonymous] en el controlador de nivel de cualquier [Authorize] se pasará por alto los atributos en el mismo
controlador, o en cualquier acción en él.
Autorización basada en roles en ASP.NET Core
22/06/2018 • 4 minutes to read • Edit Online
Cuando se crea una identidad puede pertenecer a uno o más roles. Por ejemplo, Tracy puede pertenecer a los
roles de administrador y usuario aunque Scott solo puede pertenecer al rol de usuario. ¿Cómo se crean y
administran estas funciones depende del almacén de copias de seguridad del proceso de autorización. Las
funciones se exponen al programador a través de la IsInRole método en el ClaimsPrincipal clase.
[Authorize(Roles = "Administrator")]
public class AdministrationController : Controller
{
}
Puede especificar varios roles como una lista separada por comas:
[Authorize(Roles = "HRManager,Finance")]
public class SalaryController : Controller
{
}
Este controlador sería solo sea accesible por usuarios que son miembros de la HRManager rol o la Finance rol.
Si aplica varios atributos de un usuario que obtiene acceso debe ser miembro de todos los roles especificados; el
ejemplo siguiente requiere que un usuario debe ser miembro de la PowerUser y ControlPanelUser rol.
[Authorize(Roles = "PowerUser")]
[Authorize(Roles = "ControlPanelUser")]
public class ControlPanelController : Controller
{
}
Puede limitar aún más el acceso mediante la aplicación de atributos de autorización de rol adicionales en el nivel
de acción:
[Authorize(Roles = "Administrator, PowerUser")]
public class ControlPanelController : Controller
{
public ActionResult SetTime()
{
}
[Authorize(Roles = "Administrator")]
public ActionResult ShutDown()
{
}
}
En los miembros de fragmento de código anterior de la Administrator rol o la PowerUser rol puede tener acceso
al controlador y el SetTime acción, pero solo los miembros de la Administrator rol puede tener acceso a la
ShutDown acción.
También puede bloquear un controlador pero permitir el acceso anónimo, no autenticado a acciones individuales.
[Authorize]
public class ControlPanelController : Controller
{
public ActionResult SetTime()
{
}
[AllowAnonymous]
public ActionResult Login()
{
}
}
services.AddAuthorization(options =>
{
options.AddPolicy("RequireAdministratorRole", policy => policy.RequireRole("Administrator"));
});
}
[Authorize(Policy = "RequireAdministratorRole")]
public IActionResult Shutdown()
{
return View();
}
Si desea especificar varios roles permitidos en un requisito, a continuación, puede especificar como parámetros a
la RequireRole método:
options.AddPolicy("ElevatedRights", policy =>
policy.RequireRole("Administrator", "PowerUser", "BackupAdministrator"));
En este ejemplo se autoriza a los usuarios que pertenecen a la Administrator , PowerUser o BackupAdministrator
roles.
Autorización basada en notificaciones en ASP.NET
Core
22/06/2018 • 6 minutes to read • Edit Online
Cuando se crea una identidad se pueden asignar una o más notificaciones emitidos por una entidad de confianza.
Una notificación es un par nombre-valor que representa qué el asunto, puede hacerlo no qué el sujeto. Por
ejemplo, puede tener un permiso de conducción, emitido por una entidad de licencia de conducir local. De
conducir su permiso tiene tu fecha de nacimiento. En este caso, el nombre de notificación sería DateOfBirth , el
valor de notificación sería tu fecha de nacimiento, por ejemplo 8th June 1970 y el emisor sería la autoridad de
licencia de conducir. Autorización basada en notificaciones, en su forma más sencilla, comprueba el valor de una
notificación y permite el acceso a un recurso basado en ese valor. Por ejemplo, si desea que el proceso de
autorización el acceso a un club nocturno puede ser:
El responsable de seguridad de la puerta evalúe el valor de la fecha de nacimiento notificación y si confía en el
emisor (la entidad de licencia determinante) antes de conceder que acceso.
Una identidad puede contener varias notificaciones con varios valores y puede contener varias notificaciones del
mismo tipo.
services.AddAuthorization(options =>
{
options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));
});
}
El AuthorizeAttribute atributo sólo se puede aplicar a un controlador todo, en esta instancia de la directiva de
coincidencia de identidades tendrán permiso para acceder a una acción en el controlador.
[Authorize(Policy = "EmployeeOnly")]
public class VacationController : Controller
{
public ActionResult VacationBalance()
{
}
}
Si tiene un controlador que está protegido por el AuthorizeAttribute de atributo, pero desea permitir el acceso
anónimo a acciones concretas que se aplica los AllowAnonymousAttribute atributo.
[Authorize(Policy = "EmployeeOnly")]
public class VacationController : Controller
{
public ActionResult VacationBalance()
{
}
[AllowAnonymous]
public ActionResult VacationPolicy()
{
}
}
La mayoría de notificaciones vienen con un valor. Puede especificar una lista de valores permitidos al crear la
directiva. En el ejemplo siguiente se realizaría correctamente solo para los empleados cuyo número de empleado
era 1, 2, 3, 4 o 5.
services.AddAuthorization(options =>
{
options.AddPolicy("Founders", policy =>
policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5"));
});
}
[Authorize(Policy = "HumanResources")]
public ActionResult UpdateSalary()
{
}
}
En el ejemplo anterior cualquier identidad que cumple el EmployeeOnly directiva puede tener acceso a la Payslip
acción como esa directiva se aplica en el controlador. Sin embargo para llamar a la UpdateSalary acción debe
cumplir la identidad ambos el EmployeeOnly directiva y la HumanResources directiva.
Si desea que las directivas más complicadas, como llevar a cabo una fecha de nacimiento notificación, calcular una
edad de ella, a continuación, comprobar la edad es 21 o anterior, a continuación, tiene que escribir controladores
de directiva personalizada.
Autorización basada en directivas en ASP.NET Core
22/06/2018 • 11 minutes to read • Edit Online
services.AddAuthorization(options =>
{
options.AddPolicy("AtLeast21", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(21)));
});
}
En el ejemplo anterior, se crea una directiva de "AtLeast21". Tiene un requisito único—de una antigüedad
mínima, que se proporciona como un parámetro al requisito.
Las directivas se aplican mediante el [Authorize] atributo con el nombre de la directiva. Por ejemplo:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
[Authorize(Policy = "AtLeast21")]
public class AlcoholPurchaseController : Controller
{
public IActionResult Login() => View();
Requisitos
Un requisito de autorización es una colección de parámetros de datos que puede usar una directiva para evaluar
la entidad de seguridad del usuario actual. En nuestra directiva de "AtLeast21", el requisito es un parámetro
único—la antigüedad mínima. Implementa un requisito IAuthorizationRequirement, que es una interfaz de
marcador vacío. Un requisito de antigüedad mínima con parámetros podría implementarse como sigue:
using Microsoft.AspNetCore.Authorization;
NOTE
No tiene un requisito que tienen datos o propiedades.
Controladores de autorización
Un controlador de autorización es responsable de la evaluación de propiedades de un requisito. El controlador
de autorización evalúa los requisitos con proporcionado AuthorizationHandlerContext para determinar si se
permite el acceso.
Puede tener un requisito varios controladores. Se puede heredar un controlador
AuthorizationHandler<TRequirement >, donde TRequirement es el requisito para procesarse. Como alternativa,
puede implementar un controlador IAuthorizationHandler para administrar más de un tipo de requisito.
Usar un controlador para uno de los requisitos
El siguiente es un ejemplo de una relación uno a uno en el que un controlador de antigüedad mínima emplea un
requisito único:
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;
using System;
using System.Security.Claims;
using System.Threading.Tasks;
El código anterior determina si la entidad de seguridad del usuario actual tiene una fecha de nacimiento de
notificación de que se ha emitido por un emisor conocido y de confianza. No se puede realizar la autorización
cuando falta la notificación, en cuyo caso se devuelve una tarea completa. Cuando está presente una notificación,
se calcula la edad del usuario. Si el usuario cumple con la antigüedad mínima definida por el requisito, la
autorización se considere correcta. Cuando se realiza correctamente, la autorización context.Succeed se invoca
con el requisito satisfecho como su único parámetro.
Usar un controlador para varios requisitos
El siguiente es un ejemplo de una relación de uno a varios en el que un controlador de permiso utiliza tres
requisitos:
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;
return true;
}
return true;
}
}
Recorre el código anterior PendingRequirements—una propiedad que contiene requisitos no marcado como
correcta. Si el usuario tiene permiso de lectura, que debe ser un propietario o un patrocinador para tener acceso
al recurso solicitado. Si el usuario tiene editar o eliminar permiso, debe un propietario para tener acceso al
recurso solicitado. Cuando se realiza correctamente, la autorización context.Succeed se invoca con el requisito
satisfecho como su único parámetro.
Registro del controlador
Los controladores se registran en la colección de servicios durante la configuración. Por ejemplo:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthorization(options =>
{
options.AddPolicy("AtLeast21", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(21)));
});
services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
}
using Microsoft.AspNetCore.Authorization;
BadgeEntryHandler.cs
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;
using System.Security.Claims;
using System.Threading.Tasks;
TemporaryStickerHandler.cs
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;
using System.Security.Claims;
using System.Threading.Tasks;
Asegúrese de que ambos controladores estén registrado. Si cualquier controlador se ejecuta correctamente
cuando una directiva se evalúa como el BuildingEntryRequirement , la evaluación de directivas se realiza
correctamente.
services.AddSingleton<IAuthorizationHandler, LoggingAuthorizationHandler>();
Una instancia de la voluntad de controlador se crea cuando se inicia la aplicación y se DI inyectar registrado
ILoggerFactory en su constructor.
NOTE
Los controladores que usan Entity Framework no deben registrarse como singleton.
Autorización basada en recursos en ASP.NET Core
22/06/2018 • 8 minutes to read • Edit Online
Estrategia de autorización depende de los recursos que se obtiene acceso. Considere la posibilidad de un
documento que tiene una propiedad de autor. Solo el autor tiene permiso para actualizar el documento. Por lo
tanto, el documento se debe recuperar desde el almacén de datos antes de que puede producirse la evaluación de
autorización.
Evaluación de atributo se produce antes del enlace de datos y antes de la ejecución del controlador de la página o
acción que carga el documento. Por estos motivos, autorización declarativa con un [Authorize] atributo no son
suficientes. En su lugar, puede invocar un método de autorización personalizado—un estilo que se conoce como
autorización imperativa.
Use la aplicaciones de ejemplo (cómo descargar) para explorar las características descritas en este tema.
Crear una aplicación de ASP.NET Core con datos de usuario protegidos por autorización contiene una aplicación
de ejemplo que utiliza la autorización basada en recursos.
IAuthorizationService tiene dos AuthorizeAsync sobrecargas del método: acepta un recurso y, a continuación, el
nombre de directiva y el otro aceptando el recurso y una lista de requisitos para evaluar.
ASP.NET Core 2.x
ASP.NET Core 1.x
NOTE
El código siguiente ejemplos se supone la autenticación se ha ejecutado y establezca el User propiedad.
if (Document == null)
{
return new NotFoundResult();
}
if (authorizationResult.Succeeded)
{
return Page();
}
else if (User.Identity.IsAuthenticated)
{
return new ForbidResult();
}
else
{
return new ChallengeResult();
}
}
return Task.CompletedTask;
}
}
services.AddMvc();
services.AddAuthorization(options =>
{
options.AddPolicy("EditPolicy", policy =>
policy.Requirements.Add(new SameAuthorRequirement()));
});
services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationCrudHandler>();
services.AddScoped<IDocumentRepository, DocumentRepository>();
Requisitos operativos
Si está realizando decisiones basadas en los resultados de operaciones CRUD (creación, lectura, actualización,
eliminación), use la OperationAuthorizationRequirement clase auxiliar. Esta clase permite escribir un único
controlador en lugar de una clase individual para cada tipo de operación. Para usarla, proporcionar algunos
nombres de operación:
return Task.CompletedTask;
}
}
El controlador anterior valida la operación con el recurso, la identidad del usuario y el requisito Name propiedad.
Para llamar a un controlador de recursos operativa, especifique la operación al invocar AuthorizeAsync en el
controlador de la página o la acción. En el ejemplo siguiente se determina si se permite al usuario autenticado para
ver el documento proporcionado.
NOTE
El código siguiente ejemplos se supone la autenticación se ha ejecutado y establezca el User propiedad.
if (Document == null)
{
return new NotFoundResult();
}
if (authorizationResult.Succeeded)
{
return Page();
}
else if (User.Identity.IsAuthenticated)
{
return new ForbidResult();
}
else
{
return new ChallengeResult();
}
}
Si la autorización se realiza correctamente, se devuelve la página para ver el documento. Si se produce un error de
autorización, pero el usuario se autentica, devolver ForbidResult informa a cualquier middleware de autenticación
error de autorización. Un ChallengeResult se devuelve cuando se debe realizar la autenticación. Para los clientes
de explorador interactivo, puede ser adecuado redirigir al usuario a una página de inicio de sesión.
Autorización basada en la vista de MVC de ASP.NET
Core
22/06/2018 • 2 minutes to read • Edit Online
A menudo, un programador desea mostrar, ocultar o modifique una interfaz de usuario en función de la identidad
del usuario actual. Puede acceder al servicio de autorización en las vistas MVC a través de inyección de
dependencia. Para insertar el servicio de autorización en una vista Razor, use la @inject directiva:
@using Microsoft.AspNetCore.Authorization
@inject IAuthorizationService AuthorizationService
Si desea que el servicio de autorización en cada vista, coloque el @inject la directiva en el _ViewImports.cshtml
archivos de la vistas directory. Para más información, vea Dependency injection into views (Inserción de
dependencias en vistas).
Usar el servicio de autorización insertado para invocar AuthorizeAsync exactamente del mismo modo que se
protegerían durante autorización basada en recursos:
ASP.NET Core 2.x
ASP.NET Core 1.x
En algunos casos, el recurso será el modelo de vista. Invocar AuthorizeAsync exactamente del mismo modo que se
protegerían durante autorización basada en recursos:
ASP.NET Core 2.x
ASP.NET Core 1.x
En el código anterior, el modelo se pasa como un recurso que se debe realizar la evaluación de directivas en
consideración.
WARNING
No confíe en alternancia visibilidad de los elementos de interfaz de usuario de la aplicación como la comprobación de
autorización única. Ocultar un elemento de interfaz de usuario puede impedir totalmente el acceso a su acción de
controlador asociado. Por ejemplo, considere el botón en el fragmento de código anterior. Un usuario puede invocar la
Edit dirección URL del método de acción si conozca el recurso relativo es /Document/Edit/1. Por este motivo, la Edit
método de acción debe realizar su propia comprobación de autorización.
Autorizar con un esquema específico de ASP.NET
Core
22/06/2018 • 4 minutes to read • Edit Online
En algunos escenarios, como aplicaciones de una única página (SPAs), es habitual usar varios métodos de
autenticación. Por ejemplo, la aplicación puede usar la autenticación basada en cookies para iniciar sesión y
autenticación de portador JWT para las solicitudes de JavaScript. En algunos casos, la aplicación puede tener
varias instancias de un controlador de autenticación. Por ejemplo, dos controladores de la cookie donde uno
contiene una identidad básica y el otro se crea cuando se ha desencadenado una autenticación multifactor (MFA).
MFA se puede desencadenar porque el usuario solicitó una operación que requiere seguridad adicional.
ASP.NET Core 2.x
ASP.NET Core 1.x
Un esquema de autenticación se denomina cuando se configura el servicio de autenticación durante la
autenticación. Por ejemplo:
services.AddAuthentication()
.AddCookie(options => {
options.LoginPath = "/Account/Unauthorized/";
options.AccessDeniedPath = "/Account/Forbidden/";
})
.AddJwtBearer(options => {
options.Audience = "http://localhost:5001/";
options.Authority = "http://localhost:5000/";
});
En el código anterior, se han agregado dos controladores de autenticación: uno para las cookies y otro para
portador.
NOTE
Especificar el esquema predeterminado de resultados en el HttpContext.User propiedad que se establece para esa
identidad. Si no se desea este comportamiento, puede deshabilitarlo mediante la invocación de la forma sin parámetros de
AddAuthentication .
En el ejemplo anterior, los controladores de la cookie y el portador ejecutan y tienen una oportunidad para crear
y agregar una identidad para el usuario actual. Mediante la especificación de un único esquema, se ejecuta el
controlador correspondiente.
ASP.NET Core 2.x
ASP.NET Core 1.x
[Authorize(AuthenticationSchemes =
JwtBearerDefaults.AuthenticationScheme)]
public class MixedController : Controller
En el código anterior, se ejecuta sólo el controlador con el esquema "Portador". Se omite cualquier identidad
basada en cookies.
services.AddAuthorization(options =>
{
options.AddPolicy("Over18", policy =>
{
policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
policy.RequireAuthenticatedUser();
policy.Requirements.Add(new MinimumAgeRequirement());
});
});
En el ejemplo anterior, la directiva de "Over18" solo se ejecuta con la identidad creada por el controlador
"Portador". Usar la directiva estableciendo la [Authorize] del atributo Policy propiedad:
[Authorize(Policy = "Over18")]
public class RegistrationController : Controller
Protección de datos en ASP.NET Core
21/06/2018 • 2 minutes to read • Edit Online
Las aplicaciones Web a menudo se necesitan almacenar datos confidenciales de seguridad. Windows proporciona
DPAPI para aplicaciones de escritorio, pero esto no es apropiada para las aplicaciones web. La pila de protección
de datos de ASP.NET Core proporcionan una API cifrada sencilla y fácil de usar un programador puede utilizar
para proteger los datos, incluida la rotación y la administración de claves.
La pila de protección de datos de ASP.NET Core está diseñada para que actúe como la sustituirá a largo plazo
para la <machineKey> elemento en ASP.NET 1.x - 4.x. Se diseñó para solucionar muchos de los defectos de la pila
cifrado anterior al proporcionar una solución para la mayoría de los casos de uso de aplicaciones modernas están
probables que encuentre.
Filosofía de diseño
Hemos iniciado mediante la identificación de problemas con la pila del existente. Una vez que tuvimos, hemos
evaluar el panorama de las soluciones existentes y concluye que no hay ninguna solución existente bastante
tuvieron las capacidades que se busca. A continuación, diseñamos una solución basada en varias orientaciones.
El sistema debe ofrecer la sencillez de configuración. Lo ideal es que el sistema sería sin configuración y los
desarrolladores podrían llegar a cero que se ejecuta. En situaciones donde los desarrolladores deben
configurar un aspecto concreto (por ejemplo, el repositorio de claves), se debe prestar atención especial a
hacer que esas configuraciones específicas simple.
Ofrecen una API sencilla de consumo. Las API deben ser fáciles de usar correctamente y difíciles de usar
de forma incorrecta.
Los desarrolladores no deben aprender los principios de administración de claves. El sistema debe
controlar la selección de algoritmo y la vigencia de la clave en nombre del desarrollador. Lo ideal es que el
programador ni siquiera debe tener acceso a material de la clave sin formato.
Las claves deben estar protegidas en reposo cuando sea posible. El sistema debe determinar un
mecanismo de protección predeterminado adecuado y lo aplicará automáticamente.
Con estos principios en cuenta que desarrollamos una sencilla fácil de usar pila de protección de datos.
La API de protección de datos de ASP.NET Core no están pensados principalmente para la persistencia indefinido
de cargas confidenciales. Otras tecnologías, como Windows CNG DPAPI y Azure Rights Management son más
adecuados para el escenario de almacenamiento indefinido, y tienen capacidades de administración de claves
seguro según corresponda. Es decir, no hay nada prohibir a un desarrollador usa las API de protección de datos
de ASP.NET Core para la protección a largo plazo de información confidencial.
Audiencia
El sistema de protección de datos se divide en cinco paquetes principales. Destino de distintos aspectos de estas
API tres audiencias principales;
1. El información general de las API de consumidor a los desarrolladores de aplicaciones y un marco de
destino.
"No desea obtener información acerca de cómo funciona la pila o acerca de cómo esté configurada.
Simplemente desea realizar alguna operación como simple forma como sea posible con una probabilidad
alta de usar las API correctamente."
2. El API de configuración dirigidas a los desarrolladores de aplicaciones y los administradores del sistema.
"Necesito indicar que el sistema de protección de datos que mi entorno requiere configuración o las rutas
de acceso no predeterminada".
3. Desarrolladores de destino de las API de extensibilidad responsable de implementar la directiva
personalizada. Uso de estas API se usaría limitarse a situaciones excepcionales y experimentado, los
desarrolladores de cuenta de seguridad.
"Necesito reemplazar un componente completo dentro del sistema porque tienen requisitos de
comportamiento verdaderamente únicos. Estoy dispuesto a aprender uncommonly utiliza partes de la
superficie de API para compilar un complemento que cumple los requisitos de mi."
Como sus datos más sencillas, protección consta de los siguientes pasos:
1. Crear un datos protector desde un proveedor de protección de datos.
2. Llame a la Protect método con los datos que desea proteger.
3. Llame a la Unprotect método con los datos que desea convertir en texto sin formato.
La mayoría de los marcos y modelos de aplicación, por ejemplo, ASP.NET o SignalR, ya que configurar el sistema
de protección de datos y agregarlo a un contenedor de servicios que se puede acceder a través de la inserción de
dependencias. El siguiente ejemplo muestra cómo configurar un contenedor de servicios para la inyección de
dependencia y registrar la pila de protección de datos, recibe el proveedor de protección de datos a través de DI,
crear un protector y datos de protección y desprotección
using System;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.DependencyInjection;
/*
* SAMPLE OUTPUT
*
* Enter input: Hello world!
* Protect returned: CfDJ8ICcgQwZZhlAlTZT...OdfH66i1PnGmpCR5e441xQ
* Unprotect returned: Hello world!
*/
Cuando se crea un protector debe proporcionar uno o varios propósito cadenas. Una cadena de propósito
proporciona aislamiento entre los consumidores. Por ejemplo, un protector creado con una cadena de fin de
"verde" no podrá desproteger los datos proporcionados por un protector con un propósito de "púrpura".
TIP
Instancias de IDataProtectionProvider y IDataProtector son seguras para subprocesos para varios de los llamadores.
Se ha diseñado que una vez que un componente obtiene una referencia a un IDataProtector mediante una llamada a
CreateProtector , utilizará dicha referencia para varias llamadas a Protect y Unprotect .
Una llamada a Unprotect iniciará CryptographicException si no se puede comprobar o descifrar la carga protegida.
Algunos componentes que desee omitir los errores durante la desproteger operaciones; un componente que lee las cookies
de autenticación puede controlar este error y tratar la solicitud como si no tuviera en absoluto ninguna cookie en lugar de
producirá un error en la solicitud directamente. Los componentes que desea este comportamiento deben detectar
específicamente CryptographicException en lugar de admitir todas las excepciones.
API de consumidor para ASP.NET Core
21/06/2018 • 2 minutes to read • Edit Online
El IDataProtectionProvider y IDataProtector interfaces son las interfaces básicas a través del cual los
consumidores utilizan el sistema de protección de datos. Se encuentran en el
Microsoft.AspNetCore.DataProtection.Abstractions paquete.
IDataProtectionProvider
La interfaz del proveedor representa la raíz del sistema de protección de datos. Directamente no puede usarse
para proteger o desproteger los datos. En su lugar, el consumidor debe obtener una referencia a un
IDataProtector mediante una llamada a IDataProtectionProvider.CreateProtector(purpose) , donde el propósito
es una cadena que describe el caso de uso previsto del consumidor. Vea propósito cadenas para mucho más
información sobre la intención de este parámetro y cómo elegir un valor adecuado.
IDataProtector
La interfaz de protector devuelto por una llamada a CreateProtector y de esta interfaz que los consumidores
pueden usar para realizar proteger y desproteger las operaciones.
Para proteger un elemento de datos, pasar los datos a la Protect método. La interfaz básica define un método
que byte convierte [] -> byte [], pero no hay también una sobrecarga (proporcionada como un método de
extensión) que convierte la cadena -> cadena. La seguridad proporcionada por los dos métodos es idéntica; el
desarrollador debe elegir cualquier sobrecarga es más conveniente para sus casos de uso. Con independencia de
la sobrecarga elegida, el valor devuelto por el proteja método ahora está protegido (descifra y compatible con
tecnologías de manipulaciones) y la aplicación puede enviar a un cliente no es de confianza.
Para desproteger un elemento de datos protegidos anteriormente, pasar los datos protegidos en el Unprotect
método. (Hay byte []-basada en cadena y basados en sobrecargas para mayor comodidad de desarrollador.) Si la
carga protegida fue generada por una llamada anterior a Protect en esta misma IDataProtector , el Unprotect
método devolverá la carga sin protección original. Si la carga protegida se ha alterado o ha sido creada por otro
IDataProtector , el Unprotect método producirá CryptographicException.
El concepto de misma frente a diferentes IDataProtector ties hacia el concepto de propósito. Si dos
IDataProtector instancias se generaron desde la misma raíz IDataProtectionProvider , pero a través de las
cadenas de propósito diferente en la llamada a IDataProtectionProvider.CreateProtector , considera que está
diferentes protectores, y uno no podrá desproteger cargas generados por el otro.
/*
* SAMPLE OUTPUT
*
* Enter input: Hello world!
* Protect returned: CfDJ8ICcgQwZZhlAlTZT...OdfH66i1PnGmpCR5e441xQ
* Unprotect returned: Hello world!
*/
TIP
Instancias de IDataProtectionProvider y IDataProtector son seguras para subprocesos para varios de los llamadores.
Se ha diseñado que una vez que un componente obtiene una referencia a un IDataProtector mediante una llamada a
CreateProtector , utilizará dicha referencia para varias llamadas a Protect y Unprotect . Una llamada a Unprotect
iniciará CryptographicException si no se puede comprobar o descifrar la carga protegida. Algunos componentes que desee
omitir los errores durante la desproteger operaciones; un componente que lee las cookies de autenticación puede controlar
este error y tratar la solicitud como si no tuviera en absoluto ninguna cookie en lugar de producirá un error en la solicitud
directamente. Los componentes que desea este comportamiento deben detectar específicamente CryptographicException
en lugar de admitir todas las excepciones.
Cadenas de propósito en ASP.NET Core
22/06/2018 • 6 minutes to read • Edit Online
Componentes que consumen IDataProtectionProvider debe pasar un único fines parámetro para el
CreateProtector método. Los fines parámetro es inherente a la seguridad del sistema de protección de datos,
ya que proporciona aislamiento entre los consumidores de cifrado, incluso si las claves criptográficas de raíz son
los mismos.
Cuando un consumidor especifica un propósito, la cadena de fin se usa junto con las claves criptográficas de
raíz para derivar criptográficas subclaves únicas para ese consumidor. Esto permite aislar el consumidor de
todos los otros consumidores de cifrado en la aplicación: ningún otro componente puede leer sus cargas y no se
puede leer cargas de cualquier otro componente. Este aislamiento también presenta factible todas las categorías
de un ataque contra el componente.
En el diagrama anterior, IDataProtector instancias A y B no leer de todas las demás cargas, solo sus propios.
La cadena de fin no tiene que ser secreto. Simplemente debe ser único en el sentido de que ningún otro
componente con buen comportamiento nunca proporcione la misma cadena de fin.
TIP
Con el nombre de espacio de nombres y el tipo del componente consumir las API de protección de datos es una buena
regla general, al igual que en la práctica que esta información nunca estará en conflicto.
Un componente creado por Contoso que es responsable de minting tokens de portador puede usar
Contoso.Security.BearerToken como cadena de su propósito. O - incluso mejor -, podría usar
Contoso.Security.BearerToken.v1 como cadena de su propósito. Anexar el número de versión permite que una versión
futura usar Contoso.Security.BearerToken.v2 como su propósito, y las distintas versiones sería completamente aisladas
entre sí como ir de cargas.
Desde el parámetro de fines CreateProtector es una matriz de cadenas, los pasos anteriores se haya en su
lugar especificados como [ "Contoso.Security.BearerToken", "v1" ] . Esto permite establecer una jerarquía de
propósitos y se abre la posibilidad de escenarios de varios inquilinos con el sistema de protección de datos.
WARNING
Componentes no deben permitir proporcionados por el usuario de confianza como el único origen de entrada de la
cadena con fines.
Por ejemplo, considere la posibilidad de un componente Contoso.Messaging.SecureMessage que es responsable de
almacenar mensajes seguros. Si el componente de mensajería seguro llamase a CreateProtector([ username ]) , a
continuación, un usuario malintencionado podría crear una cuenta con el nombre de usuario
"Contoso.Security.BearerToken" en un intento de obtener el componente para llamar a
CreateProtector([ "Contoso.Security.BearerToken" ]) , lo que sin darse cuenta y la mensajería segura sistema que
lleva cargas que se puede percibir como tokens de autenticación.
Una cadena con fines mejor para el componente de mensajería sería
CreateProtector([ "Contoso.Messaging.SecureMessage", "User: username" ]) , lo que proporciona aislamiento
adecuado.
NOTE
No estamos pensando en el caso de que algún componente intencionadamente elige una cadena de propósito que se
sabe que entran en conflicto con otro componente. Esos componentes básicamente se consideraría malintencionado, y
este sistema no está diseñado para proporcionar garantías de seguridad en caso de que código malintencionado ya se
está ejecutando dentro del proceso de trabajo.
Jerarquía de propósito y la arquitectura multiempresa
en ASP.NET Core
22/06/2018 • 3 minutes to read • Edit Online
Puesto que un IDataProtector también es implícitamente una IDataProtectionProvider , con fines se pueden
encadenar juntas. En este sentido, provider.CreateProtector([ "purpose1", "purpose2" ]) es equivalente a
provider.CreateProtector("purpose1").CreateProtector("purpose2") .
Esto permite algunas relaciones jerárquicas interesantes a través del sistema de protección de datos. En el ejemplo
anterior de Contoso.Messaging.SecureMessage, puede llamar el componente SecureMessage
provider.CreateProtector("Contoso.Messaging.SecureMessage") inicial una vez y almacenar en caché el resultado en
privado _myProvide campo. Protectores futuras, a continuación, pueden crearse a través de llamadas a
_myProvider.CreateProtector("User: username") , y se utilizan estos protectores para proteger los mensajes
individuales.
Esto también se voltea. Considere la posibilidad de una sola aplicación lógica qué hosts de varios inquilinos (un
CMS parece razonable) y cada inquilino pueden configurarse con su propio sistema de administración de
autenticación y el estado. La aplicación aglutina tiene un proveedor de maestro único y llama
provider.CreateProtector("Tenant 1") y provider.CreateProtector("Tenant 2") para proporcionar a cada inquilino
de su propio segmento aislado del sistema de protección de datos. Los inquilinos, a continuación, pudieron derivar
sus propios protectores individuales según sus propias necesidades, pero con independencia de forma rígida
intentan no pueden crear protectores que estén en conflicto con otro inquilino en el sistema. Esto se representa
gráficamente las indicadas a continuación.
WARNING
Se supone el paraguas de controles de la aplicación qué API están disponibles a los inquilinos individuales y que los
inquilinos no pueden ejecutar código arbitrario en el servidor. Si un inquilino puede ejecutar código arbitrario, podrían
realizar la reflexión privada para interrumpir las garantías de aislamiento, o que simplemente podrían leer directamente el
material de claves principal y derivan de las subclaves lo desean.
/*
* SAMPLE OUTPUT
*
* Enter a password: Xtw9NMgx
* Salt: NZsP6NnmfBuYeJrrAKNuVQ==
* Hashed: /OOoOer10+tGwTRDTrQSoeCxVTFr6dtYly7d0cPxIak=
*/
Vea el código fuente de ASP.NET Core Identity PasswordHasher caso de uso de tipo para un mundo real.
Limite la duración de las cargas protegidas en
ASP.NET Core
22/06/2018 • 5 minutes to read • Edit Online
Existen escenarios donde desea que el desarrollador de aplicaciones para crear una carga protegida que expira
tras un período de tiempo establecido. Por ejemplo, la carga protegida podría representar un token de
restablecimiento de contraseña que solo debe ser válido durante una hora. Es posible para el desarrollador puede
crear su propio formato de carga que contiene una fecha de expiración incrustados, y los desarrolladores
avanzados que desee hacerlo de todos modos, pero para la mayoría de los desarrolladores administrar estos
caducidad puede crecer tedioso.
Para facilitar esta tarea para nuestros clientes de desarrollador, el paquete
Microsoft.AspNetCore.DataProtection.Extensions contiene las API de la utilidad para crear cargas de expiran
automáticamente tras un período de tiempo establecido. Estas API de bloqueo de la ITimeLimitedDataProtector
tipo.
Uso de la API
El ITimeLimitedDataProtector es la interfaz principal para proteger y desproteger cargas de tiempo limitado /
expiración automática. Para crear una instancia de un ITimeLimitedDataProtector , primero necesitará una instancia
de una normal IDataProtector construido con un propósito específico. Una vez el IDataProtector instancia esté
disponible, llame a la IDataProtector.ToTimeLimitedDataProtector método de extensión para devolver un protector
con capacidades integradas de expiración.
ITimeLimitedDataProtector expone los siguientes métodos de superficie y extensión de API:
CreateProtector (propósito de cadena): ITimeLimitedDataProtector - esta API es similar a la existente
IDataProtectionProvider.CreateProtector en que se puede utilizar para crear finalidad cadenas desde un
protector de tiempo limitado de raíz.
Proteger (byte [] texto simple, expiración de DateTimeOffset): byte]
Proteger (texto simple de byte [], duración del intervalo de tiempo): byte]
Proteger (como texto simple de byte []): byte]
Proteger (texto simple de cadena, expiración de DateTimeOffset): cadena
Proteger (texto simple de cadena, duración del intervalo de tiempo): cadena
Proteger (texto simple de la cadena): cadena
Además de los principales Protect métodos que toman sólo el texto no cifrado, hay nuevas sobrecargas que
permiten especificar la fecha de expiración de la carga. La fecha de expiración se puede especificar como una fecha
absoluta (a través de un DateTimeOffset ) o como una hora relativa (desde el sistema actual time y a través de un
TimeSpan ). Si se llama a una sobrecarga que no toma una expiración, se supone la carga nunca a punto de expirar.
WARNING
No se recomienda usar estas API para proteger las cargas que requieren la persistencia a largo plazo o indefinida.
"¿Permitirme para que las cargas protegidas ser irrecuperables permanentemente después de un mes?" puede actuar como
una buena regla general; Si la respuesta es no a los desarrolladores a continuación, deben considerar las API alternativas.
El ejemplo siguiente utiliza la las rutas de código no DI para crear instancias del sistema de protección de datos.
Para ejecutar este ejemplo, asegúrese de que ha agregado una referencia al paquete
Microsoft.AspNetCore.DataProtection.Extensions en primer lugar.
using System;
using System.IO;
using System.Threading;
using Microsoft.AspNetCore.DataProtection;
// wait 6 seconds and perform another unprotect, demonstrating that the payload self-expires
Console.WriteLine("Waiting 6 seconds...");
Thread.Sleep(6000);
timeLimitedProtector.Unprotect(protectedData);
}
}
/*
* SAMPLE OUTPUT
*
* Enter input: Hello!
* Protected data: CfDJ8Hu5z0zwxn...nLk7Ok
* Round-tripped data: Hello!
* Waiting 6 seconds...
* <<throws CryptographicException with message 'The payload expired at ...'>>
*/
Desproteger cargas cuyas claves se han revocado en
ASP.NET Core
22/06/2018 • 5 minutes to read • Edit Online
La API de protección de datos de ASP.NET Core no están pensados principalmente para la persistencia indefinido
de cargas confidenciales. Otras tecnologías, como Windows CNG DPAPI y Azure Rights Management son más
adecuados para el escenario de almacenamiento indefinido, y tienen capacidades de administración de claves
seguro según corresponda. Es decir, no hay nada prohibir a un desarrollador usa las API de protección de datos de
ASP.NET Core para la protección a largo plazo de información confidencial. Claves nunca se eliminan desde el
anillo de clave, por lo que IDataProtector.Unprotect siempre se puede recuperar cargas existentes siempre que las
claves sean disponible y es válido.
Sin embargo, un problema se produce cuando el programador intenta desproteger los datos que se ha protegido
con una clave revocada, como IDataProtector.Unprotect se iniciará una excepción en este caso. Esto podría ser
bien para las cargas breves o temporales (por ejemplo, tokens de autenticación), tal y como fácilmente se pueden
volver a crear estos tipos de cargas por el sistema y en el peor el visitante del sitio podría ser necesario volver a
iniciar sesión. Pero para cargas persistentes, tener Unprotect throw podría provocar la pérdida de datos aceptable.
IPersistedDataProtector
Para admitir el escenario de permitir cargas que se debe desproteger incluso de cara a revocados claves, el
sistema de protección de datos contiene un IPersistedDataProtector tipo. Para obtener una instancia de
IPersistedDataProtector , simplemente obtener una instancia de IDataProtector en el modo normal y conversión
de try el IDataProtector a IPersistedDataProtector .
NOTE
No todos los IDataProtector instancias pueden convertirse a IPersistedDataProtector . Los programadores deben
utilizar el C# como operador o similar evitar las excepciones en tiempo de ejecución deberse a conversiones no válidas y que
deben estar preparado para controlar el caso de fallo adecuadamente.
Esta API toma la carga protegida (como una matriz de bytes) y devuelve la carga sin protección. No hay ninguna
sobrecarga basada en cadena. Los dos parámetros de salida son los siguientes.
requiresMigration : se establece en true si la clave utilizada para proteger esta carga ya no es la clave
predeterminada activa, por ejemplo, la clave utilizada para proteger esta carga es antigua y tiene una
operación de reversión de clave desde tendrán lugar. El llamador puede llamar a considere la posibilidad de
volver a proteger la carga de la función de sus necesidades empresariales.
wasRevoked : se establecerá en true si la clave utilizada para proteger esta carga se ha revocado.
WARNING
Debe extremar las precauciones al pasar ignoreRevocationErrors: true a la DangerousUnprotect método. Si después
de llamar a este método la wasRevoked valor es true, a continuación, la clave utilizada para proteger esta carga ha sido
revocada y autenticidad de la carga debe tratarse como sospechosa. En este caso, solo seguir funcionando en la carga sin
protección si tienen seguridad independiente que es auténtica, por ejemplo, que procede de una base de datos segura en
lugar de enviarse por un cliente web no es de confianza.
using System;
using System.IO;
using System.Text;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using Microsoft.Extensions.DependencyInjection;
// get a reference to the key manager and revoke all keys in the key ring
var keyManager = services.GetService<IKeyManager>();
Console.WriteLine("Revoking all keys in the key ring...");
keyManager.RevokeAllKeys(DateTimeOffset.Now, "Sample revocation.");
/*
* SAMPLE OUTPUT
*
* Input: Hello!
* Protected payload: CfDJ8LHIzUCX1ZVBn2BZ...
* Round-tripped payload: Hello!
* Revoking all keys in the key ring...
* Calling Unprotect...
* CryptographicException: The key {...} has been revoked.
* Calling DangerousUnprotect...
* Unprotected payload: Hello!
* Requires migration = True, was revoked = True
*/
Configuración de la protección de datos en ASP.NET
Core
21/06/2018 • 2 minutes to read • Edit Online
Consulte estos temas para obtener información sobre la configuración de la protección de datos en ASP.NET Core:
Configurar la protección de datos en ASP.NET Core
Una introducción a la configuración de la protección de datos de ASP.NET Core.
Administración y duración de las claves de protección de datos
Información sobre la administración y duración de las claves de protección de datos.
Compatibilidad con la directiva a nivel de equipo para la protección de datos
Obtenga más información sobre cómo configurar una directiva predeterminada a nivel de equipo para
todas las aplicaciones que usan la protección de datos.
Escenarios no compatibles con DI para la protección de datos en ASP.NET Core
Cómo usar el tipo concreto DataProtectionProvider para usar la protección de datos sin tener que pasar a
través de rutas de código específicas de DI.
Configurar la protección de datos de ASP.NET
Core
22/06/2018 • 18 minutes to read • Edit Online
WARNING
De forma similar a los archivos de configuración, el anillo de clave de protección de datos deben protegerse utilizando
los permisos adecuados. Puede elegir cifrar las claves en reposo, pero esto no evita que los atacantes de crear nuevas
claves. Por lo tanto, la seguridad de la aplicación se ve afectado. La ubicación de almacenamiento configurada con
protección de datos debe tener su acceso limitado a la propia aplicación similar a la manera en que protege a los
archivos de configuración. Por ejemplo, si decide almacenar el anillo de clave en el disco, utilice permisos del sistema de
archivos. Asegurar solo la identidad en que se ejecuta la aplicación web se lectura, escritura y crear el acceso a ese
directorio. Si utiliza almacenamiento de tabla de Azure, solo la aplicación web debe tener la capacidad de leer, escribir o
crear nuevas entradas en el almacén de tablas, etcetera.
El método de extensión AddDataProtection devuelve un IDataProtectionBuilder. IDataProtectionBuilder expone
métodos de extensión que se pueden encadenar juntos para configurar la protección de datos de opciones.
ProtectKeysWithAzureKeyVault
Para almacenar las claves en el almacén de claves de Azure, configurar el sistema con
ProtectKeysWithAzureKeyVault en la Startup clase:
PersistKeysToFileSystem
Para almacenar las claves en un recurso compartido UNC en lugar de en el % LOCALAPPDATA % ubicación
predeterminada, configure el sistema con PersistKeysToFileSystem:
WARNING
Si cambia la ubicación de persistencia de clave, el sistema cifra ya no automáticamente claves en reposo, ya que no sabe
si DPAPI es un mecanismo de cifrado adecuado.
ProtectKeysWith*
Puede configurar el sistema para proteger las claves en reposo mediante una llamada a cualquiera de los
ProtectKeysWith* API de configuración. Tenga en cuenta el ejemplo siguiente, que almacena las claves en un
recurso compartido UNC y cifra estas claves en reposo con un certificado X.509 concreto:
Vea clave de cifrado de datos almacenados para obtener más ejemplos e información sobre los mecanismos
de cifrado de clave integrado.
SetDefaultKeyLifetime
Para configurar el sistema para usar una vigencia de clave de 14 días en lugar del predeterminado de 90 días,
use SetDefaultKeyLifetime:
DisableAutomaticKeyGeneration
Puede que tenga un escenario donde no desea que una aplicación para revertir automáticamente las claves
(crear nuevas claves) tal y como se aproximen a expiración. Un ejemplo de esto podría ser aplicaciones
configuradas en una relación principal/secundario, donde solo la aplicación principal es responsable de
problemas de administración de claves y aplicaciones secundarias solo tienen una vista de solo lectura del
anillo de clave. Las aplicaciones secundarias pueden configurarse para tratar el anillo de clave como de solo
lectura configurando el sistema con DisableAutomaticKeyGeneration:
services.AddDataProtection()
.UseCryptographicAlgorithms(
new AuthenticatedEncryptorConfiguration()
{
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
});
TIP
Algoritmos de cambio no afecta a las claves existentes en el anillo de clave. Solo afecta a las claves recién generado.
// Specified in bits
EncryptionAlgorithmKeySize = 256,
Por lo general el *propiedades de tipo deben apuntar a concreto, las implementaciones (a través de un
constructor sin parámetros público) se pueden crear instancias de SymmetricAlgorithm y
KeyedHashAlgorithm, aunque el casos de especial del sistema, como algunos valores typeof(Aes) para su
comodidad.
NOTE
El SymmetricAlgorithm debe tener una longitud de clave de 128 bits ≥ y un tamaño de bloque de 64 bits ≥ y debe
admitir el cifrado del modo CBC con relleno PKCS #7. El KeyedHashAlgorithm debe tener un tamaño de síntesis de > =
128 bits, y debe ser compatible con claves de una longitud igual a la longitud de texto implícita del algoritmo de hash.
El KeyedHashAlgorithm no es estrictamente necesario que sea HMAC.
services.AddDataProtection()
.UseCustomCryptographicAlgorithms(
new CngCbcAuthenticatedEncryptorConfiguration()
{
// Passed to BCryptOpenAlgorithmProvider
EncryptionAlgorithm = "AES",
EncryptionAlgorithmProvider = null,
// Specified in bits
EncryptionAlgorithmKeySize = 256,
// Passed to BCryptOpenAlgorithmProvider
HashAlgorithm = "SHA256",
HashAlgorithmProvider = null
});
NOTE
El algoritmo de cifrado de bloques simétrico debe tener una longitud de clave de > = 128 bits, un tamaño de bloque
de > = 64 bits, y debe admitir el cifrado del modo CBC con relleno PKCS #7. El algoritmo hash debe tener un tamaño
de síntesis de > = 128 bits y debe ser compatible con que se abre con la BCRYPT_ALG_controlar_HMAC_indicador de
marca. El *propiedades del proveedor se pueden establecer en null para utilizar el proveedor predeterminado para el
algoritmo especificado. Consulte la BCryptOpenAlgorithmProvider documentación para obtener más información.
services.AddDataProtection()
.UseCustomCryptographicAlgorithms(
new CngGcmAuthenticatedEncryptorConfiguration()
{
// Passed to BCryptOpenAlgorithmProvider
EncryptionAlgorithm = "AES",
EncryptionAlgorithmProvider = null,
// Specified in bits
EncryptionAlgorithmKeySize = 256
});
NOTE
El algoritmo de cifrado de bloques simétrico debe tener una longitud de clave de > = 128 bits, un tamaño de bloque
de 128 bits exactamente, y debe ser compatible con cifrado de GCM. Puede establecer la EncryptionAlgorithmProvider
propiedad en null para utilizar el proveedor predeterminado para el algoritmo especificado. Consulte la
BCryptOpenAlgorithmProvider documentación para obtener más información.
Vea también
Escenarios no compatibles con DI
Directiva para toda la máquina
Administración de claves de protección de datos y la
duración en ASP.NET Core
22/06/2018 • 5 minutes to read • Edit Online
Administración de claves
La aplicación intenta detectar su entorno operativo y controlar la configuración de la clave por sí mismo.
1. Si la aplicación se hospeda en aplicaciones de Azure, las claves se conservan en el
%HOME%\ASP.NET\DataProtection-Keys carpeta. Esta carpeta está respaldada por el almacenamiento de
red y se sincroniza en todas las máquinas que hospedan la aplicación.
Las claves no están protegidas en reposo.
El DataProtection claves carpeta proporciona el anillo de clave a todas las instancias de una aplicación
en una única ranura de implementación.
Las ranuras de implementación independientes, por ejemplo, almacenamiento provisional y
producción, no comparten ningún anillo de clave. Al intercambiar las ranuras de implementación, por
ejemplo, el intercambio de ensayo a producción o usando A / B pruebas, cualquier aplicación con la
protección de datos no podrá descifrar los datos almacenados mediante el anillo de clave dentro de la
ranura anterior. Esto conduce al usuario que se haya iniciado sesión en una aplicación que usa la
autenticación con cookies ASP.NET Core estándar, porque usa protección de datos para proteger sus
cookies. Si desea llaveros independiente de la ranura, utiliza un proveedor de anillo de clave externa,
como almacenamiento de blobs de Azure, el almacén de claves de Azure, un almacén de SQL, o la
caché en Redis.
2. Si el perfil de usuario está disponible, las claves se conservan en el
%LOCALAPPDATA%\ASP.NET\DataProtection-Keys carpeta. Si el sistema operativo es Windows, las
claves se cifran en reposo con DPAPI.
3. Si la aplicación se hospeda en IIS, las claves se conservan en el registro HKLM en una clave del registro
especial que se disponen de las ACL solo a la cuenta de proceso de trabajo. Las claves se cifran en reposo
con DPAPI.
4. Si coincide con ninguna de estas condiciones, no se conservan las claves fuera del proceso actual. Cuando
el proceso se cierra, todos los genera claves se pierden.
El programador está siempre en control total y puede invalidar cómo y dónde se almacenan las claves. Las tres
primeras opciones anteriores deben proporcionar buenos valores predeterminados para la mayoría de las
aplicaciones similar a cómo ASP.NET <machineKey > rutinas de la generación automática funcionaban en el
pasado. La opción de reserva final es el único escenario que requiere que el desarrollador especificar
configuración por adelantado si quieren persistencia de clave, pero esta reserva sólo se produce en situaciones
excepcionales.
Cuando se hospedan en un contenedor de Docker, las claves deben conservarse en una carpeta que es un
volumen de Docker (un volumen compartido o un volumen montado de host que se conserva más allá de la
duración del contenedor) o en un proveedor externo, como el almacén de claves de Azure o Redis. Un proveedor
externo también es útil en escenarios de granja de servidores web si las aplicaciones no pueden obtener acceso a
un volumen compartido de red (consulte PersistKeysToFileSystem para obtener más información).
WARNING
Si el desarrollador invalida las reglas descritas anteriormente y señala el sistema de protección de datos en un repositorio
de clave específico, se deshabilita el cifrado automático de claves en reposo. Protección en rest puede habilitarse de nuevo
a través de configuración.
Vigencia de clave
Las claves tienen una duración de 90 días de forma predeterminada. Cuando expira una clave, la aplicación
genera una nueva clave automáticamente y establece la nueva clave como la clave activa. Claves retiradas
quedan en el sistema, siempre y cuando la aplicación puede descifrar los datos protegidos con ellos. Vea
administración de claves para obtener más información.
Algoritmos predeterminados
El algoritmo de protección de carga predeterminado usado es AES -256-CBC para HMACSHA256 la
confidencialidad y la autenticidad. Una clave maestra de 512 bits, cambiada cada 90 días, se utiliza para derivar
las claves secundarias dos utilizadas para estos algoritmos según una por cada carga. Vea subclave derivación
para obtener más información.
Vea también
Extensibilidad de administración de claves
Admite la directiva de todo el equipo de protección
de datos en ASP.NET Core
22/06/2018 • 7 minutes to read • Edit Online
WARNING
El administrador del sistema puede establecer la directiva predeterminada, pero no puede aplicarla. El desarrollador de
aplicaciones siempre puede reemplazar cualquier valor con uno de su propia elección. La directiva predeterminada sólo
afecta a las aplicaciones que el desarrollador no ha especificado un valor explícito para una configuración.
Tipos de cifrado
Si EncryptionType es CBC de CNG, el sistema está configurado para utilizar un cifrado por bloques simétrico
modo CBC para confidencialidad y HMAC para autenticidad con servicios proporcionados por Windows CNG
(vea especificar algoritmos personalizados de Windows CNG para más detalles). Se admiten los siguientes
valores adicionales, cada uno de los cuales corresponde a una propiedad en el tipo de
CngCbcAuthenticatedEncryptionSettings.
Si EncryptionType es CNG GCM, el sistema está configurado para usar un cifrado por bloques simétrico modo
Galois/contador para la confidencialidad y la autenticidad con servicios proporcionados por Windows CNG (vea
especificar algoritmos personalizados de Windows CNG Para obtener más información.) Se admiten los
siguientes valores adicionales, cada uno de los cuales corresponde a una propiedad en el tipo de
CngGcmAuthenticatedEncryptionSettings.
Si EncryptionType tiene cualquier otro valor distinto de null o está vacío, el sistema de protección de datos
produce una excepción durante el inicio.
WARNING
Al configurar una configuración de directiva predeterminada que afecta a los nombres de tipo (EncryptionAlgorithmType,
ValidationAlgorithmType, KeyEscrowSinks), los tipos deben ser disponibles para la aplicación. Esto significa que para
aplicaciones que se ejecutan en CLR de escritorio, los ensamblados que contienen estos tipos deben estar presentes en la
caché de ensamblados Global (GAC). Para aplicaciones de ASP.NET Core que se ejecutan en .NET Core, deben instalarse los
paquetes que contienen estos tipos.
DI no compatible con escenarios para la protección
de datos en ASP.NET Core
22/06/2018 • 4 minutes to read • Edit Online
Console.WriteLine();
Console.WriteLine("Press any key...");
Console.ReadKey();
}
}
/*
* SAMPLE OUTPUT
*
* Enter input: Hello world!
* Protect returned: CfDJ8FWbAn6...ch3hAPm1NJA
* Unprotect returned: Hello world!
*
* Press any key...
*/
De forma predeterminada, la DataProtectionProvider tipo concreto no cifra el material de clave sin procesar
antes la almacenarla en el sistema de archivos. Esto sirve para admitir escenarios donde los puntos de
desarrollador para un recurso compartido de red y el sistema de protección de datos no pueden deducir
automáticamente un mecanismo de cifrado de clave adecuado en rest.
Además, el DataProtectionProvider tipo concreto no aislar las aplicaciones de de forma predeterminada. Todas
las aplicaciones con el mismo directorio clave pueden compartir cargas siempre y cuando sus finalidad
parámetros coincide con.
El DataProtectionProvider constructor acepta una devolución de llamada de configuración opcionales que puede
usarse para ajustar los comportamientos del sistema. El ejemplo siguiente muestra el aislamiento de
restauración con una llamada explícita a SetApplicationName. El ejemplo también muestra la configuración del
sistema para cifrar automáticamente las claves permanentes mediante DPAPI de Windows. Si el directorio
apunta a un recurso compartido UNC, es recomendable que se va a distribuir un certificado compartido entre
todos los equipos correspondientes como configurar el sistema para usar el cifrado basada en certificados con
una llamada a ProtectKeysWithCertificate.
using System;
using System.IO;
using Microsoft.AspNetCore.DataProtection;
Console.WriteLine();
Console.WriteLine("Press any key...");
Console.ReadKey();
}
}
TIP
Instancias de la DataProtectionProvider tipo concreto son caros de crear. Si una aplicación mantiene varias instancias de
este tipo y si todo está usando el mismo directorio de almacenamiento de claves, puede degradar el rendimiento de la
aplicación. Si usas el DataProtectionProvider tipo, se recomienda que cree este tipo una vez y volver a usarlo tanto
como sea posible. El DataProtectionProvider tipo y todos IDataProtector instancias creadas a partir de los son seguras
para subprocesos para distintos llamadores.
API de extensibilidad de protección de datos de
ASP.NET Core
21/06/2018 • 2 minutes to read • Edit Online
WARNING
Los tipos que implementan cualquiera de las interfaces siguientes deben ser seguro para subprocesos para distintos
llamadores.
IAuthenticatedEncryptor
El IAuthenticatedEncryptor interfaz es el bloque de creación básico del subsistema criptográfico. Por lo
general, hay un IAuthenticatedEncryptor por clave y la instancia de IAuthenticatedEncryptor contiene todos los
material de clave de cifrado y algoritmo información necesaria para realizar operaciones criptográficas.
Como sugiere su nombre, el tipo es responsable de proporcionar servicios de cifrado y descifrado autenticados.
Expone las siguientes API de dos.
Descifrar (ArraySegment texto cifrado, ArraySegment additionalAuthenticatedData): byte]
Cifrar (ArraySegment texto simple, ArraySegment additionalAuthenticatedData): byte]
El método Encrypt devuelve un blob que incluye el texto sin formato descifra y una etiqueta de autenticación. La
etiqueta de autenticación debe incluir los datos adicionales autenticados (AAD ), aunque el AAD propio no
necesita poder recuperarse desde la carga final. El método Decrypt valida la etiqueta de autenticación y devuelve
la carga deciphered. Todos los errores (excepto ArgumentNullException y similar) deben homogeneizarse a
CryptographicException.
NOTE
La propia instancia IAuthenticatedEncryptor realmente no debe contener el material de clave. Por ejemplo, la
implementación puede delegar a un HSM para todas las operaciones.
Serialización XML
La diferencia principal entre IAuthenticatedEncryptor y IAuthenticatedEncryptorDescriptor es que el descriptor
sabe cómo crear el sistema de cifrado y proporcionarle argumentos válidos. Tenga en cuenta un
IAuthenticatedEncryptor cuya implementación se basa en SymmetricAlgorithm y KeyedHashAlgorithm. Trabajo
del sistema de cifrado es consumen estos tipos, pero no conoce necesariamente estos tipos de proceden, por lo
que realmente no se puede escribir una descripción de cómo volver a sí mismo si se reinicia la aplicación
adecuada. El descriptor de actúa como un nivel más alto a partir de esto. Puesto que el descriptor sabe cómo
crear la instancia de sistema de cifrado (p. ej., sabe cómo crear los algoritmos necesarios), puede serializar esa
información en forma de XML para que la instancia de sistema de cifrado se puede volver a crear después de
restablece una aplicación.
El descriptor de se puede serializar a través de su rutina de ExportToXml. Esta rutina devuelve un
XmlSerializedDescriptorInfo que contiene dos propiedades: la representación de XElement de descriptor y el tipo
que representa un IAuthenticatedEncryptorDescriptorDeserializer que puede ser se usa para restablecerse este
descriptor dada la XElement correspondiente.
El descriptor serializado puede contener información confidencial como material de clave de cifrado. El sistema de
protección de datos tiene compatibilidad integrada para cifrar la información antes de que se conservan en el
almacenamiento. Para aprovechar estas características, el descriptor debería marcar el elemento que contiene
información confidencial con el nombre de atributo "requiresEncryption" (xmlns
"http://schemas.asp.net/2015/03/dataProtection"), valor "true".
TIP
Hay una API auxiliar para establecer este atributo. Llame al método de extensión que XElement.markasrequiresencryption()
ubicado en el espacio de nombres Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.
También puede haber casos donde el descriptor serializado no contiene información confidencial. Considere la
posibilidad de nuevo el caso de una clave criptográfica que se almacenan en un HSM. El material de clave no se
puede escribir el descriptor al serializar a sí mismo porque el HSM no expone el material en formato de texto
simple. En su lugar, puede escribir el descriptor de la versión de clave ajusta de la clave (si el HSM permite la
exportación de este modo) o el identificador único del HSM para la clave.
IAuthenticatedEncryptorDescriptorDeserializer
El IAuthenticatedEncryptorDescriptorDeserializer interfaz representa un tipo que sabe cómo deserializar
una instancia de IAuthenticatedEncryptorDescriptor desde un XElement. Expone un único método:
ImportFromXml (elemento de XElement): IAuthenticatedEncryptorDescriptor
El método ImportFromXml toma el XElement que devolvió IAuthenticatedEncryptorDescriptor.ExportToXml y
crea un equivalente de la IAuthenticatedEncryptorDescriptor original.
Tipos que implementan IAuthenticatedEncryptorDescriptorDeserializer deben tener uno de los dos constructores
públicos siguientes:
.ctor(IServiceProvider)
.ctor()
NOTE
IServiceProvider pasado al constructor puede ser null.
TIP
Leer la administración de claves sección antes de leer esta sección, tal y como se explican algunos de los conceptos
fundamentales de estas API.
WARNING
Los tipos que implementan cualquiera de las interfaces siguientes deben ser seguro para subprocesos para distintos
llamadores.
Key
La IKey interfaz es la representación básica de una clave en el sistema de cifrado. La clave de término se utiliza
aquí en el sentido abstracto, no en el sentido de "material clave criptográfico" literal. Una clave tiene las siguientes
propiedades:
Fechas de expiración, la creación y la activación
Estado de revocación
Identificador de clave (GUID )
ASP.NET Core 2.x
ASP.NET Core 1.x
Además, IKey expone un CreateEncryptor método que se puede usar para crear un IAuthenticatedEncryptor
instancia asociado a esta clave.
NOTE
No hay ninguna API para recuperar el material criptográfico sin formato de un IKey instancia.
IKeyManager
El IKeyManager interfaz representa un objeto responsable de almacenamiento de claves general, la recuperación y
la manipulación. Expone tres operaciones de alto nivel:
Cree una nueva clave y almacenar los datos en almacenamiento.
Obtener todas las claves de almacenamiento.
Revocar una o varias claves y conservar la información de revocación en el almacenamiento.
WARNING
Escribir una IKeyManager una tarea muy avanzada y la mayoría de los desarrolladores no debe intentarlo. En su lugar, la
mayoría de los desarrolladores deben aprovechar las ventajas de las funciones que ofrece el XmlKeyManager clase.
XmlKeyManager
El XmlKeyManager tipo es la implementación concreta en el cuadro de IKeyManager . Proporciona varias funciones
útiles, incluidas la custodia de clave y el cifrado de claves en reposo. Las claves en este sistema se representan
como elementos XML (en concreto, XElement).
XmlKeyManager depende de otros componentes en el curso de cumplir sus tareas:
ASP.NET Core 2.x
ASP.NET Core 1.x
AlgorithmConfiguration , que determina los algoritmos utilizados por las nuevas claves.
IXmlRepository , que controla donde las claves se conservan en almacenamiento.
IXmlEncryptor [opcional], que permite cifrar las claves en reposo.
IKeyEscrowSink [opcional], que proporciona servicios de custodia de clave.
A continuación se muestran los diagramas de alto nivel que indican cómo se conectan juntos estos componentes
en XmlKeyManager .
ASP.NET Core 2.x
ASP.NET Core 1.x
IXmlRepository
El IXmlRepository interfaz representa un tipo que pueda persista código XML en y recuperar el XML de un
almacén de copia de seguridad. Expone dos API:
GetAllElements(): IReadOnlyCollection
StoreElement (elemento XElement, cadena friendlyName)
Las implementaciones de IXmlRepository no es necesario analizar el XML que se pasan a través de ellos. Debe
tratar los documentos XML como opaco y permitir que los niveles superiores a preocuparse sobre cómo generar
y analizar los documentos.
Hay dos tipos integrados concretos que implementan IXmlRepository : FileSystemXmlRepository y
RegistryXmlRepository . Consulte la documento de proveedores de almacenamiento de claves para obtener más
información. Registrar un personalizado IXmlRepository sería la manera adecuada para usar un almacén de
respaldo diferentes, por ejemplo, el almacenamiento de blobs de Azure.
Para cambiar el repositorio predeterminado de toda la aplicación, registrar un personalizado IXmlRepository
instancia:
ASP.NET Core 2.x
ASP.NET Core 1.x
IXmlEncryptor
El IXmlEncryptor interfaz representa un tipo que puede cifrar un elemento XML de texto simple. Expone una
única API:
Cifrar (plaintextElement de XElement): EncryptedXmlInfo
Si un número de serie IAuthenticatedEncryptorDescriptor contiene elementos marcados como "requiere cifrado",
a continuación, XmlKeyManager ejecutará esos elementos a través de la configurada IXmlEncryptor del Encrypt
método y se conservará el elemento descifra en lugar de la elemento de texto simple para el IXmlRepository . El
resultado de la Encrypt método es un EncryptedXmlInfo objeto. Este objeto es un contenedor que contiene tanto
el resultante descifra XElement y el tipo que representa un IXmlDecryptor que puede utilizarse para descifrar el
elemento correspondiente.
Hay cuatro tipos integrados concretos que implementan IXmlEncryptor :
CertificateXmlEncryptor
DpapiNGXmlEncryptor
DpapiXmlEncryptor
NullXmlEncryptor
IXmlDecryptor
El interfaz representa un tipo que sabe cómo descifrar un
IXmlDecryptor XElement que se descifra mediante una
IXmlEncryptor . Expone una única API:
Los tipos que implementan IXmlDecryptor debe tener uno de los dos constructores públicos siguientes:
.ctor(IServiceProvider)
.ctor()
NOTE
El IServiceProvider pasado al constructor puede ser null.
IKeyEscrowSink
El IKeyEscrowSink interfaz representa un tipo que puede realizar la custodia de la información confidencial.
Recuerde que descriptores serializados podrían contener información confidencial (por ejemplo, el material
criptográfico) y esto es lo que ha provocado la introducción de la IXmlEncryptor escriba en primer lugar. Sin
embargo, los accidentes y llaveros pueden eliminarse o están dañados.
La interfaz de custodia proporciona una trama de escape de emergencia, permitir el acceso al XML serializado sin
procesar antes de que se transforme en ninguno configurado IXmlEncryptor. La interfaz expone una única API:
Almacén (keyId de Guid, elemento de XElement)
Depende del IKeyEscrowSink implementación para controlar el elemento proporcionado de forma segura
coherente con la directiva empresarial. Una posible implementación podría ser para que el receptor de custodia
cifrar el elemento XML mediante un certificado X.509 corporativo conocido donde se ha custodiado clave privada
del certificado; el CertificateXmlEncryptor tipo puede ayudarle con esto. El IKeyEscrowSink implementación
también es responsable de conservar el elemento proporcionado de forma adecuada.
De forma predeterminada ningún mecanismo de custodia está habilitado, aunque los administradores de servidor
pueden configurarlo global. También puede configurar mediante programación a través de la
IDataProtectionBuilder.AddKeyEscrowSink método tal como se muestra en el ejemplo siguiente. El
AddKeyEscrowSink reflejado de las sobrecargas de método la IServiceCollection.AddSingleton y
IServiceCollection.AddInstance sobrecargas, como IKeyEscrowSink instancias están pensadas para ser singletons.
Si hay varios IKeyEscrowSink instancias registradas, se llamará a cada uno de ellos durante la generación de
claves, por lo que las claves se pueden custodiar a varios mecanismos simultáneamente.
No hay ninguna API para leer el material de un IKeyEscrowSink instancia. Esto es coherente con la teoría del
diseño del mecanismo de custodia: se ha diseñado para hacer que el material de clave sea accesible a una
autoridad de confianza y, puesto que la aplicación de sí mismo no es una entidad de confianza, no debería tener
acceso a su propio material custodiada.
El código de ejemplo siguiente muestra cómo crear y registrar un IKeyEscrowSink donde se custodiar claves de
modo que solo los miembros del "CONTOSODomain Admins" pueden recuperarlos.
NOTE
Para ejecutar este ejemplo, debe ser en un equipo con Windows 8 Unidos a un dominio / máquina con Windows Server
2012 y el controlador de dominio deben ser Windows Server 2012 o posterior.
using System;
using System.IO;
using System.Xml.Linq;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using Microsoft.AspNetCore.DataProtection.XmlEncryption;
using Microsoft.Extensions.DependencyInjection;
// get a reference to the key manager and force a new key to be generated
Console.WriteLine("Generating new key...");
var keyManager = services.GetService<IKeyManager>();
keyManager.CreateNewKey(
activationDate: DateTimeOffset.Now,
expirationDate: DateTimeOffset.Now.AddDays(7));
}
// A key escrow sink where keys are escrowed such that they
// can be read by members of the CONTOSO\Domain Admins group.
private class MyKeyEscrowSink : IKeyEscrowSink
private class MyKeyEscrowSink : IKeyEscrowSink
{
private readonly IXmlEncryptor _escrowEncryptor;
/*
* SAMPLE OUTPUT
*
* Generating new key...
* Escrowing key 38e74534-c1b8-4b43-aea1-79e856a822e5
* <encryptedKey>
* <!-- This key is encrypted with Windows DPAPI-NG. -->
* <!-- Rule: SID=S-1-5-21-1004336348-1177238915-682003330-512 -->
* <value>MIIIfAYJKoZIhvcNAQcDoIIIbTCCCGkCAQ...T5rA4g==</value>
* </encryptedKey>
*/
API de protección de datos de varios núcleos de
ASP.NET
22/06/2018 • 2 minutes to read • Edit Online
WARNING
Los tipos que implementan cualquiera de las interfaces siguientes deben ser seguro para subprocesos para distintos
llamadores.
ISecret
El ISecret interfaz representa un valor secreto, como material de clave de cifrado. Contiene la superficie de API
siguiente:
Length : int
Dispose() : void
El WriteSecretIntoBuffer método rellena el búfer proporcionado con el valor sin formato del secreto. El motivo de
esta API toma el búfer como un parámetro en lugar de devolver un byte[] directamente es esto da al llamador la
oportunidad para anclar el objeto de búfer, limitar la exposición de secreto para el recolector de elementos no
utilizados administrado.
El Secret tipo es una implementación concreta de ISecret donde el valor secreto se almacena en memoria en el
proceso. En plataformas de Windows, el valor secreto se cifra mediante CryptProtectMemory.
Implementación de protección de datos de ASP.NET
Core
21/06/2018 • 2 minutes to read • Edit Online
09 F0 C9 F0 80 9C 81 0C 19 66 19 40 95 36 53 F8
AA FF EE 57 57 2F 40 4C 3F 7F CC 9D CC D9 32 3E
84 17 99 16 EC BA 1F 4A A1 18 45 1F 2D 13 7A 28
79 6B 86 9C F8 B7 84 F9 26 31 FC B1 86 0A F1 56
61 CF 14 58 D3 51 6F CF 36 50 85 82 08 2D 3F 73
5F B0 AD 9E 1A B2 AE 13 57 90 C8 F5 7C 95 4E 6A
8A AA 06 EF 43 CA 19 62 84 7C 11 B2 C8 71 9D AA
52 19 2E 5B 4C 1E 54 F0 55 BE 88 92 12 C1 4B 5E
52 C9 74 A0
Desde el formato de carga por encima de los primeros 32 bits o 4 bytes son el encabezado mágico identifica la
versión (09 F0 C9 F0)
Los siguientes 128 bits o 16 bytes es el identificador de clave (80 9 81 C 0c 19 66 19 40 95 36 53 F8 AA FF EE
57)
El resto contiene la carga y es específico para el formato utilizado.
WARNING
Todas las cargas protegidas para una clave determinada se iniciará con el mismo encabezado de 20 bytes (valor mágica, Id.
de clave). Los administradores pueden usar este hecho con fines de diagnóstico para la aproximación cuando se genera una
carga. Por ejemplo, la carga anterior corresponde a la clave {0c819c80-6619-4019-9536-53f8aaffee57}. Si después de
comprobar el repositorio clave encuentra que la fecha de activación de esta clave específica fue 2015-01-01 y su fecha de
expiración era 2015-03-01, entonces es razonable suponer la carga (si no ha sido manipulado con) se ha generado dentro
de esa ventana, conceda a o tomar una pequeña factor de aglutinante a cada lado.
Subclave derivación y cifrado autenticado en
ASP.NET Core
22/06/2018 • 8 minutes to read • Edit Online
La mayoría de las teclas en el anillo de clave contiene alguna forma de entropía y tendrá algorítmica información
que indica "cifrado de modo CBC + validación HMAC" o "cifrado de GCM + validación". En estos casos, nos
referimos a la entropía incrustada como el material de creación de claves maestras (o KM ) para esta clave y
llevamos a cabo una función de derivación de claves para derivar las claves que se usará para las operaciones
criptográficas reales.
NOTE
Las claves son abstractas, y una implementación personalizada posible que no funcionen como sigue. Si la clave proporciona
su propia implementación de IAuthenticatedEncryptor en lugar de usar una de nuestras fábricas integradas, el
mecanismo se describe en esta sección ya no es aplicable.
NOTE
El IDataProtector.Protect implementación le anteponer el encabezado mágica y el Id. de clave a salida antes de
devolverlo al llamador. Dado que el encabezado mágica y el Id. de clave son implícitamente forma parte de AAD, y dado que
el modificador de tecla se introduce como entrada a KDF, esto significa que cada byte único de la última carga devuelta es
autenticado por el equipo Mac.
NOTE
Aunque GCM forma nativa es compatible con el concepto de AAD, nos estamos todavía alimentación AAD solo KDF original,
para pasar una cadena vacía a GCM para su parámetro AAD. La razón para esto es dos vertientes. En primer lugar, para
admitir la agilidad nunca queremos usar K_M directamente como la clave de cifrado. Además, GCM impone requisitos de
unicidad muy estrictos en sus entradas. La probabilidad de que la rutina de cifrado de GCM alguna vez invocado con dos o
más distintos conjuntos de datos de entrada con el mismo (clave, nonce) par no debe superar los 2 ^ 32. Si se soluciona K_E
no podemos realizar más de 2 ^ 32 operaciones de cifrado antes de que se ejecute mantiene del 2 ^ limitar -32. Esto puede
parecer un gran número de operaciones, pero un servidor web de tráfico elevado puede ir a través de solicitudes de 4 mil
millones en días simples, bien dentro de la duración normal de estas claves. A estar al día de 2 ^ límite de probabilidad-32,
seguimos utilizar un modificador de clave de 128 bits y 96 bits nonce, que extiende radicalmente el número de operaciones
puede usar para cualquier K_M determinado. Para simplificar el trabajo de diseño compartimos la ruta de acceso del código
KDF entre las operaciones de cifrado CBC y GCM y, puesto que ya se considera AAD en KDF no es necesario que se reenvíe
a la rutina GCM.
Encabezados de contexto en ASP.NET Core
22/06/2018 • 17 minutes to read • Edit Online
5B B6 C9 83 13 78 22 1D 8E 10 73 CA CF 65 8E B0
61 62 42 71 CB 83 21 DD A0 4A 05 00 5B AB C0 A2
49 6F A5 61 E3 E2 49 87 AA 63 55 CD 74 0A DA C4
B7 92 3D BF 59 90 00 A9
A continuación, calcular Enc_CBC (K_E, IV, "") de AES -192-CBC dado IV = 0 * y K_E como anteriormente.
resultado: = F474B1872B3B53E4721DE19C0841DB6F
A continuación, calcular MAC (K_H, "") para HMACSHA256 dado K_H como anteriormente.
resultado: = D4791184B996092EE1202F36E8608FA8FBD98ABDFF5402F264B1D7211536220C
Esto produce el encabezado de contexto completo siguiente:
00 00 00 00 00 18 00 00 00 10 00 00 00 20 00 00
00 20 F4 74 B1 87 2B 3B 53 E4 72 1D E1 9C 08 41
DB 6F D4 79 11 84 B9 96 09 2E E1 20 2F 36 E8 60
8F A8 FB D9 8A BD FF 54 02 F2 64 B1 D7 21 15 36
22 0C
Este encabezado de contexto es la huella digital del par de algoritmo de cifrado autenticado (cifrado de AES -192-
CBC + HMACSHA256 validación). Los componentes, como se describe anteriormente son:
el marcador (00 00)
la longitud de clave de cifrado de bloque (00 00 00 18)
el tamaño de bloque de cifrado de bloque (00 00 00 10)
la longitud de clave de HMAC (00 00 00 20)
el tamaño de la síntesis HMAC (00 00 00 20)
el cifrado por bloques salida PRP (F4 74 - DB 6F ) y
la salida de HMAC PRF (D4 79 - final).
NOTE
El cifrado del modo CBC + HMAC encabezado de contexto de autenticación se basa en la misma forma, independientemente
de si se proporcionan las implementaciones de algoritmos CNG de Windows o tipos administrados SymmetricAlgorithm y
KeyedHashAlgorithm. Esto permite que aplicaciones que se ejecutan en sistemas operativos diferentes generar de forma
confiable el mismo encabezado de contexto, aunque las implementaciones de los algoritmos difieren entre sistemas
operativos. (En la práctica, la KeyedHashAlgorithm no tiene que ser un HMAC correcto. Puede ser cualquier tipo de
algoritmo hash con clave.)
A2 19 60 2F 83 A9 13 EA B0 61 3A 39 B8 A6 7E 22
61 D9 F8 6C 10 51 E2 BB DC 4A 00 D7 03 A2 48 3E
D1 F7 5A 34 EB 28 3E D7 D4 67 B4 64
A continuación, calcular Enc_CBC (K_E, IV, "") para 3DES -192-CBC dado IV = 0 * y K_E como anteriormente.
resultado: = ABB100F81E53E10E
A continuación, calcular MAC (K_H, "") para HMACSHA1 dado K_H como anteriormente.
resultado: = 76EB189B35CF03461DDF877CD9F4B1B4D63A7555
Esto genera el encabezado de contexto completo que es una huella digital de los autenticados par de algoritmo de
cifrado (cifrado 3DES -192-CBC + validación HMACSHA1), se muestra a continuación:
00 00 00 00 00 18 00 00 00 08 00 00 00 14 00 00
00 14 AB B1 00 F8 1E 53 E1 0E 76 EB 18 9B 35 CF
03 46 1D DF 87 7C D9 F4 B1 B4 D6 3A 75 55
00 01 00 00 00 20 00 00 00 0C 00 00 00 10 00 00
00 10 E7 DC CE 66 DF 85 5A 32 3A 6B B7 BD 7A 59
BE 45
El sistema de protección de datos administra automáticamente la duración de claves maestras de usa para
proteger y desproteger cargas. Cada clave puede estar en uno de cuatro fases:
Creado: la clave existe en el anillo de clave, pero aún no se ha activado. La clave no debe utilizarse para
nuevas operaciones de protección hasta que haya transcurrido el tiempo suficiente que la clave ha tenido
la oportunidad de propagarse a todas las máquinas que consumen este anillo de clave.
Active - la clave existe en el anillo de clave y debe utilizarse para todas las operaciones de proteger de
nuevo.
Ha caducado: la clave de su duración natural ha ejecutado y ya no debe usarse para nuevas operaciones de
protección.
Revocar - la clave está en peligro y no se debe utilizar para nuevas operaciones de protección.
Claves creadas, activas y caducadas pueden utilizarse para desproteger cargas entrantes. Claves revocadas de
forma predeterminada no pueden usarse para desproteger cargas, pero el desarrollador de aplicaciones puede
invalidar este comportamiento si es necesario.
WARNING
El programador podría verse tentado a eliminar una clave desde el anillo de clave (p. ej., eliminando el archivo
correspondiente del sistema de archivos). En ese momento, todos los datos protegidos por la clave es indescifrables
permanentemente, y no hay ninguna invalidación emergencia que hay con claves revocadas. Si se elimina una clave es un
comportamiento destructivo realmente y, por consiguiente, el sistema de protección de datos no expone ninguna API de
primera clase para realizar esta operación.
services.AddDataProtection()
// use 14-day lifetime instead of 90-day lifetime
.SetDefaultKeyLifetime(TimeSpan.FromDays(14));
Un administrador también puede cambiar el valor predeterminado de todo el sistema, aunque una llamada
explícita a SetDefaultKeyLifetime invalidará cualquier directiva de todo el sistema. La vigencia de la clave
predeterminada no puede ser inferior a 7 días.
WARNING
Los desarrolladores rara vez deberían (si alguna vez) que deba usar las API de administración clave directamente. El sistema
de protección de datos llevará a cabo la administración automática de claves como se describió anteriormente.
El sistema de protección de datos expone una interfaz IKeyManager que se puede utilizar para inspeccionar y
realizar cambios en el anillo de clave. El sistema DI que proporciona la instancia de IDataProtectionProvider
también puede proporcionar una instancia de IKeyManager para su consumo. Como alternativa, puede extraer el
IKeyManager directamente desde el IServiceProvider como en el ejemplo siguiente.
Cualquier operación que modifica el anillo de clave (crear una nueva clave explícitamente o realizar una
revocación) invalidará la memoria caché en memoria. La siguiente llamada a Protect o Unprotect hará que el
sistema de protección de datos leer el anillo de clave y volver a crear la memoria caché.
El ejemplo siguiente muestra cómo utilizar el IKeyManager interfaz para inspeccionar y manipular el anillo de
clave, incluida la revocación de claves existentes y generar una nueva clave manualmente.
using System;
using System.IO;
using System.Threading;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using Microsoft.Extensions.DependencyInjection;
// add a new key to the key ring with immediate activation and a 1-month expiration
keyManager.CreateNewKey(
activationDate: DateTimeOffset.Now,
expirationDate: DateTimeOffset.Now.AddMonths(1));
Console.WriteLine("Added a new key.");
/*
* SAMPLE OUTPUT
*
* Performed a protect operation.
* The key ring contains 1 key(s).
* Key {1b948618-be1f-440b-b204-64ff5a152552}: Created = 2015-03-18 22:20:49Z, IsRevoked = False
* Revoked all existing keys.
* Added a new key.
* Added a new key.
* The key ring contains 2 key(s).
* Key {1b948618-be1f-440b-b204-64ff5a152552}: Created = 2015-03-18 22:20:49Z, IsRevoked = True
* Key {2266fc40-e2fb-48c6-8ce2-5fde6b1493f7}: Created = 2015-03-18 22:20:51Z, IsRevoked = False
*/
Almacenamiento de claves
El sistema de protección de datos tiene una heurística mediante el cual intenta deducir automáticamente una
ubicación de almacenamiento de claves adecuado y el cifrado en el mecanismo de rest. Esto también es
configurable por el desarrollador de aplicaciones. Los documentos siguientes explican las implementaciones de
forma predeterminada de estos mecanismos:
Proveedores de almacenamiento de claves de forma predeterminada
Cifrado de claves de forma predeterminada en proveedores de rest
Proveedores de almacenamiento de claves en
ASP.NET Core
22/06/2018 • 4 minutes to read • Edit Online
De forma predeterminada, el sistema de protección de datos emplea un método heurístico para determinar
dónde se debe conservar el material de clave de cifrado. El desarrollador puede invalidar la heurística y especificar
manualmente la ubicación.
NOTE
Si especifica una ubicación de persistencia de clave explícita, el sistema de protección de datos se anular el registro el cifrado
de claves de forma predeterminada en el mecanismo de rest que proporciona la heurística, por lo que ya no se cifrarán las
claves en reposo. Se recomienda que, además especificar un mecanismo de cifrado de clave explícita para las aplicaciones de
producción.
El sistema de protección de datos que se suministra con varios proveedores de almacenamiento de claves de
forma predeterminada.
Sistema de archivos
Prevemos que muchas aplicaciones usarán un repositorio de clave basada en el sistema de archivos. Para
configurar esto, llame a la PersistKeysToFileSystem rutina de configuración tal y como se muestra a continuación.
Proporcionar un DirectoryInfo que apunta al repositorio que se deben almacenar las claves.
sc.AddDataProtection()
// persist keys to a specific directory
.PersistKeysToFileSystem(new DirectoryInfo(@"c:\temp-keys\"));
La DirectoryInfo puede apuntar a un directorio en el equipo local, o puede apuntar a una carpeta en un recurso
compartido de red. Si señala a un directorio en el equipo local (y el escenario es que sólo las aplicaciones en el
equipo local tendrá que utilizar este repositorio), considere el uso de Windows DPAPI para cifrar las claves en
reposo. En caso contrario, considere el uso de un certificado X.509 para cifrar las claves en reposo.
Azure y Redis
El Microsoft.AspNetCore.DataProtection.AzureStorage y Microsoft.AspNetCore.DataProtection.Redis paquetes
permiten almacenar las claves de protección de datos en el almacenamiento de Azure o una caché de Redis. Las
claves se pueden compartir en varias instancias de una aplicación web. La aplicación de ASP.NET Core puede
compartir las cookies de autenticación o protección CSRF en varios servidores. Para configurar en Azure, llame a
uno de los PersistKeysToAzureBlobStorage sobrecargas tal y como se muestra a continuación.
services.AddMvc();
}
Vea también el código de prueba de Azure.
Para configurar en Redis, llame a uno de los PersistKeysToRedis sobrecargas tal y como se muestra a
continuación.
services.AddMvc();
}
Registro
A veces, la aplicación podría no tener acceso de escritura al sistema de archivos. Considere un escenario donde se
ejecuta una aplicación como una cuenta de servicio virtual (por ejemplo, la identidad del grupo de aplicaciones de
w3wp.exe). En estos casos, el administrador puede haber aprovisionado una clave del registro que sea adecuado
con las ACL para la identidad de la cuenta de servicio. Llame a la PersistKeysToRegistry rutina de configuración
tal y como se muestra a continuación. Proporcionar un RegistryKey que apunta a la ubicación donde se deben
almacenar valores/claves criptográficos.
sc.AddDataProtection()
// persist keys to a specific location in the system registry
.PersistKeysToRegistry(Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Sample\keys"));
Si usa el registro del sistema como un mecanismo de persistencia, considere el uso de Windows DPAPI para
cifrar las claves en reposo.
De forma predeterminada, el sistema de protección de datos emplea un método heurístico para determinar cómo
criptográfico material de clave debe cifrarse en reposo. El desarrollador puede invalidar la heurística y especificar
manualmente cómo se deben cifrar las claves en reposo.
NOTE
Si especifica un cifrado de clave explícita en el mecanismo de rest, el sistema de protección de datos se anular el registro el
mecanismo de almacenamiento de claves predeterminado que proporciona la heurística. Debe especifican un mecanismo de
almacenamiento de claves explícitas, en caso contrario, no podrá iniciar el sistema de protección de datos.
El sistema de protección de datos que se suministra con tres mecanismos de cifrado de claves de forma
predeterminada.
DPAPI de Windows
Este mecanismo solo está disponible en Windows.
Cuando se utiliza DPAPI de Windows, material de clave se cifrará a través de CryptProtectData antes de que se
conservan en el almacenamiento. DPAPI es un mecanismo de cifrado adecuado para los datos que nunca se
leerán fuera de la máquina actual (aunque es posible hacer copia de estas claves en Active Directory; vea DPAPI y
perfiles móviles). Por ejemplo configurar el cifrado de clave en el resto DPAPI.
sc.AddDataProtection()
// only the local user account can decrypt the keys
.ProtectKeysWithDpapi();
Si ProtectKeysWithDpapi se llaman sin ningún parámetro, sólo la cuenta de usuario de Windows actual puede
descifrar el material de clave persistente. Opcionalmente, puede especificar que cualquier cuenta de usuario en el
equipo (no solo la cuenta de usuario actual) debe ser capaz de descifrar el material de clave, como se muestra en
el ejemplo siguiente.
sc.AddDataProtection()
// all user accounts on the machine can decrypt the keys
.ProtectKeysWithDpapi(protectToLocalMachine: true);
Certificado X.509
Este mecanismo no está disponible en .NET Core 1.0 o 1.1 .
Si la aplicación se reparte entre varias máquinas, puede ser conveniente para distribuir un certificado X.509
compartido entre las máquinas y configurar aplicaciones para usar este certificado para el cifrado de claves en
reposo. Vea a continuación para obtener un ejemplo.
sc.AddDataProtection()
// searches the cert store for the cert with this thumbprint
.ProtectKeysWithCertificate("3BCE558E2AD3E0E34A7743EAB5AEA2A9BD2575A0");
Debido a limitaciones de .NET Framework se admiten sólo los certificados con claves privadas de CAPI. Vea
basada en certificados de cifrado con Windows DPAPI-NG a continuación para buscar posibles soluciones para
estas limitaciones.
Windows DPAPI-NG
Este mecanismo solo está disponible en Windows 8 / Windows Server 2012 y versiones posterior.
A partir de Windows 8, el sistema operativo admite DPAPI-NG (también denominado CNG DPAPI). Microsoft
distribuye su escenario de uso como se indica a continuación.
Informática en nube, sin embargo, a menudo requiere que ese contenido cifrado en un equipo pueden descifrar
en otro. Por lo tanto, a partir de Windows 8, Microsoft ampliado la idea de usar una API relativamente sencilla
para abarcar escenarios de nube. Esta nueva API, denominada DPAPI-NG, permite compartir de forma segura
secretos (claves, contraseñas, material de clave) y mensajes protegiéndolos a un conjunto de entidades de
seguridad que puede utilizarse para desproteger en equipos diferentes después de la autorización y la
autenticación correcta.
Desde sobre DPAPI CNG
La entidad de seguridad se codifica como una regla de descriptor de protección. Considere el ejemplo siguiente,
que cifra el material de clave de modo que solo el usuario unido al dominio con el SID especificado puede
descifrar el material de clave.
sc.AddDataProtection()
// uses the descriptor rule "SID=S-1-5-21-..."
.ProtectKeysWithDpapiNG("SID=S-1-5-21-...",
flags: DpapiNGProtectionDescriptorFlags.None);
También hay una sobrecarga sin parámetros de ProtectKeysWithDpapiNG . Se trata de un método útil para
especificar la regla "SID = mío", donde la extraiga es el SID de la cuenta de usuario de Windows actual.
sc.AddDataProtection()
// uses the descriptor rule "SID={current account SID}"
.ProtectKeysWithDpapiNG();
En este escenario, el controlador de dominio de AD es responsable de distribuir las claves de cifrado que se utiliza
en las operaciones de NG DPAPI. El usuario de destino podrá descifrar la carga cifrada desde cualquier equipo
unido al dominio (siempre que el proceso se ejecuta bajo su identidad).
sc.AddDataProtection()
// searches the cert store for the cert with this thumbprint
.ProtectKeysWithDpapiNG("CERTIFICATE=HashId:3BCE558E2AD3E0E34A7743EAB5AEA2A9BD2575A0",
flags: DpapiNGProtectionDescriptorFlags.None);
Cualquier aplicación que se señala en este repositorio debe ejecutarse en Windows 8.1 / Windows Server 2012
R2 o posterior para que pueda descifrar esta clave.
Cifrado de clave personalizado
Si no resultan adecuados los mecanismos de forma predeterminada, el programador puede especificar su propio
mecanismo de cifrado de claves proporcionando un personalizado IXmlEncryptor .
Inmutabilidad de clave y la configuración de clave de
ASP.NET Core
22/06/2018 • 2 minutes to read • Edit Online
Una vez que un objeto se mantiene en la memoria auxiliar, su representación de infinito es fijo. Se pueden agregar
datos nuevos a la memoria auxiliar, pero nunca pueden transformarse los datos existentes. El propósito principal
de este comportamiento es evitar daños en los datos.
Una consecuencia de este comportamiento es que, cuando una clave se escribe en la memoria auxiliar, es
inmutable. Su fecha de creación, activación y expiración nunca se puede cambiar, aunque puede revocar utilizando
IKeyManager . Además, su información algorítmica subyacente, el material de creación de claves maestras y el
cifrado en Propiedades de rest también son inmutables.
Si el desarrollador cambia cualquier configuración que afecta a la persistencia de clave, esos cambios no entran en
vigor hasta la próxima vez que se genere una clave, ya sea a través de una llamada explícita a
IKeyManager.CreateNewKey o a través de lo datos protección del sistema propio clave automática generación
comportamiento. La configuración que afecta a la persistencia de clave es los siguientes:
La vigencia de clave predeterminada
El cifrado de claves en el mecanismo de rest
La información algorítmica contenida dentro de la clave
Si necesita estas opciones para iniciar anteriores a la siguiente tecla automática gradual tiempo, considere la
posibilidad de realizar una llamada explícita a IKeyManager.CreateNewKey para forzar la creación de una nueva
clave. Recuerde que debe proporcionar una fecha de activación explícita ({ahora + 2 días} es una buena regla
general para dejar tiempo propagar el cambio) y la fecha de expiración en la llamada.
TIP
Todas las aplicaciones tocar el repositorio deben especificar la misma configuración con el IDataProtectionBuilder
métodos de extensión. De lo contrario, las propiedades de la clave persistente será depende de la aplicación en particular
que invoca las rutinas de generación de claves.
Formato de almacenamiento de claves en ASP.NET
Core
22/06/2018 • 5 minutes to read • Edit Online
En este caso, solo la clave especificada se ha revocado. Si el identificador de clave es "*", sin embargo, como en el
ejemplo siguiente, se revocan todas las claves cuya fecha de creación es anterior a la fecha de revocación
especificada.
El <motivo > elemento nunca se lee por el sistema. Es simplemente un lugar conveniente para almacenar un
motivo de revocación legible.
Proveedores de protección de datos efímero en
ASP.NET Core
22/06/2018 • 2 minutes to read • Edit Online
Existen escenarios donde una aplicación necesita un throwaway IDataProtectionProvider . Por ejemplo, solo se
puede experimentar el desarrollador en una aplicación de consola de uso único o la propia aplicación es transitoria
(se incluye en el script o una prueba unitaria de proyecto). Para admitir estos escenarios el
Microsoft.AspNetCore.DataProtection paquete incluye un tipo EphemeralDataProtectionProvider . Este tipo
proporciona una implementación básica de IDataProtectionProvider cuya clave repositorio se mantiene
solamente en memoria y no escribe en ningún almacén de respaldo.
Cada instancia de EphemeralDataProtectionProvider usa su propia clave principal único. Por lo tanto, si un
IDataProtector con raíz en un EphemeralDataProtectionProvider genera una carga protegida, ese carga solo puede
desproteger un equivalente IDataProtector (les proporciona el mismo propósito cadena) con raíz en el mismo
EphemeralDataProtectionProvider instancia.
El siguiente ejemplo muestra cómo crear instancias de un EphemeralDataProtectionProvider y usarla para proteger
y desproteger los datos.
using System;
using Microsoft.AspNetCore.DataProtection;
/*
* SAMPLE OUTPUT
*
* Enter input: Hello!
* Protect returned: CfDJ8AAAAAAAAAAAAAAAAAAAAA...uGoxWLjGKtm1SkNACQ
* Unprotect returned: Hello!
* << throws CryptographicException >>
*/
Compatibilidad en ASP.NET Core
21/06/2018 • 2 minutes to read • Edit Online
Para instalar el nuevo sistema de protección de datos en un proyecto de 4.5.1+ ASP.NET existente, instale el
paquete Microsoft.AspNetCore.DataProtection.SystemWeb. Esto creará una instancia del sistema de protección
de datos mediante la configuración predeterminada configuración.
Cuando se instala el paquete, inserta una línea en Web.config que le indica a ASP.NET para usarla para más
operaciones criptográficas, como la autenticación de formularios, estado de vista y llamadas a MachineKey.Protect.
La línea que se inserta quede como sigue.
TIP
Puede indicar si el nuevo sistema de protección de datos está activo mediante la inspección de campos como __VIEWSTATE ,
que debe comenzar por "CfDJ8" en el ejemplo siguiente. "CfDJ8" es la representación base64 del encabezado de magia "09
F0 C9 F0" que identifica una carga protegida por el sistema de protección de datos.
Configuración de paquetes
El sistema de protección de datos se crea una instancia con una configuración predeterminada del programa de
instalación de cero. Sin embargo, puesto que de forma predeterminada, las claves se conservan al sistema de
archivos local, esto no funcionará para las aplicaciones que se implementan en una granja de servidores. Para
resolver este problema, puede proporcionar la configuración mediante la creación de un tipo que las subclases
DataProtectionStartup e invalida su método ConfigureServices.
A continuación se muestra un ejemplo de un tipo de inicio de protección de datos personalizado que configura
tanto donde se conservan las claves, y cómo está cifrados en reposo. También invalida la directiva de aislamiento
de aplicaciones predeterminado proporcionando su propio nombre de la aplicación.
using System;
using System.IO;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.SystemWeb;
using Microsoft.Extensions.DependencyInjection;
namespace DataProtectionDemo
{
public class MyDataProtectionStartup : DataProtectionStartup
{
public override void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection()
.SetApplicationName("my-app")
.PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\myapp-keys\"))
.ProtectKeysWithCertificate("thumbprint");
}
}
}
TIP
También puede usar <machineKey applicationName="my-app" ... /> en lugar de una llamada explícita a
SetApplicationName. Se trata de un mecanismo de comodidad para evitar la fuerza al desarrollador para crear un tipo
derivado de DataProtectionStartup si todos los que deseaban configurar se establecen el nombre de la aplicación.
Para habilitar esta configuración personalizada, vuelva al archivo Web.config y busque la <appSettings> elemento
que instalar el paquete agregado al archivo de configuración. Tendrá una apariencia similar el siguiente marcado:
<appSettings>
<!--
If you want to customize the behavior of the ASP.NET Core Data Protection stack, set the
"aspnet:dataProtectionStartupType" switch below to be the fully-qualified name of a
type which subclasses Microsoft.AspNetCore.DataProtection.SystemWeb.DataProtectionStartup.
-->
<add key="aspnet:dataProtectionStartupType" value="" />
</appSettings>
Rellene el valor en blanco con el nombre completo de ensamblado del tipo derivado de DataProtectionStartup
que acaba de crear. Si el nombre de la aplicación es DataProtectionDemo, esto sería el siguiente.
<add key="aspnet:dataProtectionStartupType"
value="DataProtectionDemo.MyDataProtectionStartup, DataProtectionDemo" />
El sistema de protección de datos recién configurada ahora está listo para su uso dentro de la aplicación.
Exigir HTTPS en el núcleo de ASP.NET
22/06/2018 • 9 minutes to read • Edit Online
WARNING
Hacer no usar RequireHttpsAttribute en las API Web que reciben información confidencial. RequireHttpsAttribute usa
códigos de estado HTTP para redirigir exploradores de HTTP a HTTPS. Los clientes de API no pueden entender o siguen las
redirecciones de HTTP a HTTPS. Estos clientes pueden enviar información a través de HTTP. Las API Web deben realizar las
tareas:
No escuchar en HTTP.
Cierre la conexión con el código de estado 400 (solicitud incorrecta) y no atender la solicitud.
Requerir HTTPS
Se recomienda que todas las aplicaciones web de ASP.NET Core llamará Middleware de redirección de HTTPS
(UseHttpsRedirection) para redirigir todas las solicitudes HTTP a HTTPS.
El código siguiente llama UseHttpsRedirection en la Startup clase:
[!code-csharp]
El código siguiente llama AddHttpsRedirection para configurar las opciones de middleware:
[!code-csharp]
El código resaltado anterior:
Conjuntos de HttpsRedirectionOptions.RedirectStatusCode a Status307TemporaryRedirect , que es el valor
predeterminado. Deben llamar aplicaciones de producción UseHsts.
Establece el puerto HTTPS en 5001. El valor predeterminado es 443.
Los siguientes mecanismos establecen automáticamente el puerto:
El software intermedio puede detectar los puertos a través de IServerAddressesFeature cuando se aplican
las condiciones siguientes:
Se utiliza kestrel o HTTP.sys directamente con los puntos de conexión HTTPS (también se aplica a la
aplicación se ejecuta con el depurador de código de Visual Studio).
Solo un puerto HTTPS se utiliza la aplicación.
Se utiliza Visual Studio:
IIS Express tiene habilitados para HTTPS.
launchSettings.json establece el sslPort de IIS Express.
NOTE
Cuando una aplicación se ejecute detrás de un proxy inverso (por ejemplo, IIS, IIS Express), IServerAddressesFeature no
está disponible. El puerto debe configurarse manualmente. Cuando el puerto no está configurado, no se redirigen las
solicitudes.
NOTE
El puerto puede configurarse indirectamente estableciendo la dirección URL con el ASPNETCORE_URLS variable de entorno.
La variable de entorno configura el servidor y, a continuación, el middleware indirectamente detecta el puerto HTTPS a
través de IServerAddressesFeature .
NOTE
Una alternativa al uso de Middleware de redirección de HTTPS ( UseHttpsRedirection ) consiste en usar el Middleware de
reescritura de dirección URL ( AddRedirectToHttps ). AddRedirectToHttps puede establecer el código de estado y el
puerto cuando se ejecuta la redirección. Para obtener más información, consulte Middleware de reescritura de dirección
URL.
Al redirigir a HTTPS sin necesidad de reglas de redirección adicionales, se recomienda usar HTTPS redirección Middleware (
UseHttpsRedirection ) se describe en este tema.
[!code-csharp]
El código resaltado anterior requiere que todas las solicitudes usar HTTPS ; por lo tanto, se omiten las solicitudes
HTTP. El código resaltado siguiente redirige todas las solicitudes HTTP a HTTPS:
[!code-csharp]
Para obtener más información, consulte Middleware de reescritura de dirección URL. El middleware también
permite a la aplicación para establecer el código de estado o el código de estado y el puerto cuando se ejecuta la
redirección.
Requerir HTTPS globalmente ( options.Filters.Add(new RequireHttpsAttribute()); ) es una práctica
recomendada de seguridad. Aplicar el [RequireHttps] atributo a todas las páginas de Razor/controladores no
considera tan seguro como requerir HTTPS globalmente. No puede garantizar la [RequireHttps] atributo se
aplica cuando se agregan nuevos controladores y las páginas de Razor.
Protocolo de seguridad de transporte estrictos de HTTP (HSTS)
Por OWASP, seguridad de transporte estricta de HTTP (HSTS ) supone una mejora de seguridad opcional que
se especifica mediante una aplicación web mediante el uso de un encabezado de respuesta especial. Una vez
que un explorador compatible recibe este encabezado ese explorador impide que todas las comunicaciones se
envíen a través de HTTP para el dominio especificado y en su lugar, le enviará todas las comunicaciones a través
de HTTPS. También evita que haga clic en HTTPS a través de solicitudes en exploradores.
ASP.NET Core 2.1 o posterior implementa HSTS con el UseHsts método de extensión. El código siguiente llama
UseHsts cuando la aplicación no se encuentra en modo de desarrollo:
[!code-csharp]
UseHsts no se recomienda en el desarrollo porque el encabezado HSTS es alta almacenable en caché
exploradores. De forma predeterminada, UseHsts excluye la dirección de bucle invertido local.
El código siguiente:
[!code-csharp]
Establece el parámetro precarga del encabezado de seguridad de transporte Strict. Precarga no es parte de la
especificación RFC HSTS, pero es compatible con los exploradores web para cargar previamente sitios HSTS
en instalación nueva. Para más información, vea https://hstspreload.org/.
Permite includeSubDomain, que aplica la directiva HSTS a subdominios de Host.
Establece explícitamente el parámetro de max-age del encabezado de seguridad de transporte Strict a 60
días. Si no se establece, el valor predeterminado es 30 días. Consulte la max-age directiva para obtener más
información.
Agrega example.com a la lista de hosts que se van a excluir.
UseHsts excluye los siguientes hosts de bucle invertido:
localhost : La dirección de bucle invertido de IPv4.
127.0.0.1 : La dirección de bucle invertido de IPv4.
[::1] : La dirección de bucle invertido de IPv6.
// This method gets called by the runtime. Use this method to add services
// to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies
// is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
// This method gets called by the runtime. Use this method to configure the
// HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc();
}
}
// This method gets called by the runtime. Use this method to add services
// to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies
// is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
// This method gets called by the runtime. Use this method to configure the
// HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc();
}
}
@{
var consentFeature = Context.Features.Get<ITrackingConsentFeature>();
var showBanner = !consentFeature?.CanTrack ?? false;
var cookieString = consentFeature?.CreateConsentCookie();
}
@if (showBanner)
{
<nav id="cookieConsent" class="navbar navbar-default navbar-fixed-top" role="alert">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-
target="#cookieConsent .navbar-collapse">
<span class="sr-only">Toggle cookie consent banner</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<span class="navbar-brand"><span class="glyphicon glyphicon-info-sign" aria-hidden="true">
</span></span>
</div>
<div class="collapse navbar-collapse">
<p class="navbar-text">
Use this space to summarize your privacy and cookie use policy.
</p>
<div class="navbar-right">
<a asp-page="/Privacy" class="btn btn-info navbar-btn">Learn More</a>
<button type="button" class="btn btn-default navbar-btn" data-cookie-
string="@cookieString">Accept</button>
</div>
</div>
</div>
</nav>
<script>
(function () {
document.querySelector("#cookieConsent button[data-cookie-string]").addEventListener("click",
function (el) {
document.cookie = el.target.dataset.cookieString;
document.querySelector("#cookieConsent").classList.add("hidden");
}, false);
})();
</script>
}
Este parcial:
Obtiene el estado de seguimiento para el usuario. Si la aplicación se configura para requerir el consentimiento
que del usuario debe dar su consentimiento antes de que pueden realizar el seguimiento de las cookies. Si se
requiere consentimiento, el cromo de consentimiento de la cookie se fija en la barra de navegación que se creó
en la parte superior del Pages/Shared/_Layout.cshtml archivo.
Proporciona una etiqueta HTML <p> usar la directiva de elemento que se va a resumir su privacidad y
cookies.
Proporciona un vínculo a Pages/Privacy.cshtml donde puede detallar política de privacidad de su sitio.
Cookies esenciales
Si no se ha proporcionado el consentimiento, solo las cookies de marcado esenciales se envían al explorador. El
código siguiente realiza una cookie esenciales:
public IActionResult OnPostCreateEssentialAsync()
{
HttpContext.Response.Cookies.Append(Constants.EssentialSec,
DateTime.Now.Second.ToString(),
new CookieOptions() { IsEssential = true });
ResponseCookies = Response.Headers[HeaderNames.SetCookie].ToString();
return RedirectToPage("./Index");
}
Estado de sesión cookies no son esenciales. Estado de sesión no es funcional cuando se deshabilita el
seguimiento.
Datos personales
Las aplicaciones de ASP.NET Core creadas con cuentas de usuario individuales incluyen código para descargar y
eliminar datos personales.
Seleccione el nombre de usuario y, a continuación, seleccione datos personales:
Notas:
Para generar el Account/Manage código, vea Scaffold identidad.
Eliminar y descargar impacto solamente los datos de identidad de manera predeterminada. Los datos de
usuario personalizado de creación de aplicaciones deben extenderse para delete/descargar los datos de
usuario personalizada. Problema de GitHub cómo agregar o eliminar datos de usuario personalizado a la
identidad realiza un seguimiento de un artículo propuesto acerca de cómo crear
personalizado/eliminar/descarga de datos de usuario personalizada. Si le gustaría que ese tema un nivel de
prioridad, deje un Pulgar hacia arriba reacción en el problema.
Guarda los tokens para el usuario que se almacenan en la tabla de base de datos de identidad
AspNetUserTokens se eliminan cuando el usuario se elimina mediante el comportamiento de eliminación en
cascada debido a la clave externa.
Cifrado en reposo
Algunas bases de datos y mecanismos de almacenamiento permiten para el cifrado en reposo. Cifrado en reposo:
Cifra automáticamente los datos almacenados.
Cifra sin configuración, programación u otras tareas para el software que tiene acceso a los datos.
Es la opción más sencilla y más segura.
Permite que la base de datos administre las claves y el cifrado.
Por ejemplo:
Microsoft SQL y SQL Azure proporcionan cifrado de datos transparente (TDE ).
SQL Azure cifra la base de datos de forma predeterminada
Azure Blobs, archivos, tabla y cola de almacenamiento se cifran de forma predeterminada.
Para las bases de datos que no proporcionan cifrado integrado en reposo, es posible que pueda usar el cifrado del
disco para proporcionar la misma protección. Por ejemplo:
BitLocker para Windows Server
Linux:
eCryptfs
EncFS.
Recursos adicionales
Microsoft.com/GDPR
Ubicación de almacenamiento segura de
secretos de la aplicación en el desarrollo de
ASP.NET Core
23/06/2018 • 14 minutes to read • Edit Online
Variables de entorno
Las variables de entorno se utilizan para evitar el almacenamiento de secretos de la aplicación en el
código o en archivos de configuración local. Las variables de entorno invalidan los valores de
configuración de todos los orígenes de configuración especificada anteriormente.
Configurar la lectura de los valores de variables de entorno mediante una llamada a
AddEnvironmentVariables en el Startup constructor:
[!code-csharp]
Piense en una aplicación web de ASP.NET Core en la que cuentas de usuario individuales está
habilitada la seguridad. Una cadena de conexión de base de datos predeterminada se incluye en el
proyecto appSettings.JSON que se archivo con la clave DefaultConnection . La cadena de conexión
predeterminada es LocalDB, que se ejecuta en modo de usuario y no requiere una contraseña. Durante la
implementación de aplicaciones, el DefaultConnection clave-valor puede reemplazarse por el valor de la
variable de entorno. La variable de entorno puede almacenar la cadena de conexión completa con las
credenciales confidenciales.
WARNING
Las variables de entorno se almacenan normalmente en texto sin formato y sin cifrar. Si la máquina o el proceso se
ve comprometido, confianza pueden tener acceso a las variables de entorno. Saber qué medidas adicionales para
evitar la divulgación de información confidencial del usuario pueden ser necesarias.
Administrador de secreto
La herramienta Administrador de secreto almacena datos confidenciales durante el desarrollo de un
proyecto de ASP.NET Core. En este contexto, una parte de los datos confidenciales es un secreto de la
aplicación. Secretos de la aplicación se almacenan en una ubicación independiente desde el árbol del
proyecto. Los secretos de la aplicación se asociada a un proyecto específico o se comparten entre varios
proyectos. Los secretos de aplicación no se protegen en el control de origen.
WARNING
La herramienta Administrador de secreto no cifra los secretos almacenados y no debe tratarse como un almacén
de confianza. Es solo con fines de desarrollo. Las claves y los valores se almacenan en un archivo de configuración
de JSON en el directorio del perfil de usuario.
En los anteriores, las rutas de acceso de archivo, reemplace <user_secrets_id> con el UserSecretsId
valor especificado en el .csproj archivo.
No escriba código que depende de la ubicación o el formato de datos que se guardan con la herramienta
Administrador de secreto. Pueden cambiar estos detalles de implementación. Por ejemplo, los valores
secretos no se cifran, pero pudieron ser en el futuro.
TIP
Ejecute dotnet --version desde un shell de comandos para ver el número de versión de .NET Core SDK
instalado.
The tool 'Microsoft.Extensions.SecretManager.Tools' is now included in the .NET Core SDK. Information
on resolving this warning is available at (https://aka.ms/dotnetclitools-in-box).
dotnet user-secrets -h
Options:
-?|-h|--help Show help information
--version Show version information
-v|--verbose Show verbose output
-p|--project <PROJECT> Path to project. Defaults to searching the current directory.
-c|--configuration <CONFIGURATION> The project configuration to use. Defaults to 'Debug'.
--id The user secret ID to use.
Commands:
clear Deletes all the application secrets
list Lists all the application secrets
remove Removes the specified user secret
set Sets the user secret to the specified value
Use "dotnet user-secrets [command] --help" for more information about a command.
NOTE
Debe estar en el mismo directorio que el .csproj archivo para ejecutar las herramientas que se definen en el .csproj
del archivo DotNetCliToolReference elementos.
Establecer un secreto
La herramienta Administrador de secreto opera en valores de configuración específicos del proyecto
almacenados en su perfil de usuario. Para utilizar secretos del usuario, definir una UserSecretsId
elemento dentro de un PropertyGroup de la .csproj archivo. El valor de UserSecretsId es arbitrario, pero
es único para el proyecto. Los desarrolladores suelen generan un GUID para el UserSecretsId .
[!code-xml]
[!code-xml]
TIP
En Visual Studio, haga clic en el proyecto en el Explorador de soluciones y seleccione administrar secretos del
usuario en el menú contextual. Este movimiento agrega un UserSecretsId elemento, que se rellena con un
GUID a la .csproj archivo. Visual Studio abre un secrets.json archivo en el editor de texto. Reemplace el contenido
de secrets.json con los pares clave-valor que se almacenará. Por ejemplo:
json { "Movies": { "ServiceApiKey": "12345", "ConnectionString": "Server=
(localdb)\\mssqllocaldb;Database=Movie-1;Trusted_Connection=True;MultipleActiveResultSets=true" }
}
.
Definir un secreto de la aplicación que consta de una clave y su valor. El secreto está asociado con el
proyecto UserSecretsId valor. Por ejemplo, ejecute el siguiente comando desde el directorio en el que el
.csproj archivo existe:
En el ejemplo anterior, los dos puntos denota que Movies es un objeto literal con un ServiceApiKey
propiedad.
La herramienta Administrador de secreto puede utilizarse desde otros directorios demasiado. Use la
--project opción para proporcionar la ruta de acceso del sistema de archivos en el que el .csproj archivo
existe. Por ejemplo:
[!code-csharp]
Cuando CreateDefaultBuilder no llama durante la construcción de host, agregue el origen de
configuración de usuario secretos con una llamada a AddUserSecrets en el Startup constructor:
[!code-csharp]
Secretos del usuario se pueden recuperar a través de la Configuration API:
[!code-csharp]
[!code-csharp]
Un enfoque más seguro consiste en almacenar la contraseña como un secreto. Por ejemplo:
Quitar el Password par de clave y valor de la cadena de conexión en appSettings.JSON que se. Por
ejemplo:
{
"ConnectionStrings": {
"Movies": "Server=(localdb)\\mssqllocaldb;Database=Movie-1;User
Id=johndoe;MultipleActiveResultSets=true"
}
}
{
"Movies": {
"ServiceApiKey": "12345",
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=Movie-
1;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
Movies:ServiceApiKey = 12345
Movies:ConnectionString = Server=(localdb)\mssqllocaldb;Database=Movie-
1;Trusted_Connection=True;MultipleActiveResultSets=true
En el ejemplo anterior, un signo de dos puntos en los nombres de clave denota la jerarquía de objetos
dentro de secrets.json.
{
"Movies": {
"ServiceApiKey": "12345",
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=Movie-
1;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
La aplicación secrets.json archivo se modificó para quitar el par de clave y valor asociado a la
MoviesConnectionString clave:
{
"Movies": {
"ServiceApiKey": "12345"
}
}
Movies:ServiceApiKey = 12345
{
"Movies": {
"ServiceApiKey": "12345",
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=Movie-
1;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
{}
Package
Para usar el proveedor, agregue una referencia a la Microsoft.Extensions.Configuration.AzureKeyVault paquete.
Configuración de aplicación
Puede explorar el proveedor con el aplicaciones de ejemplo. Una vez que establezca un almacén de claves y
crear secretos en el almacén, las aplicaciones de ejemplo segura cargar los valores de secreto en sus
configuraciones y mostrarlos en las páginas Web.
El proveedor se agrega a la ConfigurationBuilder con el AddAzureKeyVault extensión. En las aplicaciones de
ejemplo, la extensión utiliza tres valores de configuración cargados desde el appSettings.JSON que se archivo.
keyVaultConfigBuilder.AddAzureKeyVault(
$"https://{builtConfig["Vault"]}.vault.azure.net/",
builtConfig["ClientId"],
builtConfig["ClientSecret"]);
config.AddConfiguration(keyVaultConfig);
})
.UseStartup<Startup>();
3. Ejecute la aplicación de ejemplo, que obtiene sus valores de configuración de IConfigurationRoot con el
mismo nombre que el nombre de secreto.
Valores no son jerárquicos: el valor de SecretName se obtiene con config["SecretName"] .
Valores jerárquicos (secciones): Use : notación (coma) o el GetSection método de extensión. Use
cualquiera de estos métodos para obtener el valor de configuración:
config["Section:SecretName"]
config.GetSection("Section")["SecretName"]
Cuando se ejecuta la aplicación, una página Web muestra los valores cargados secretos:
WARNING
No utilizar prefijos en secretos del almacén de claves colocar secretos para varias aplicaciones en el mismo almacén de
claves o colocar secretos entorno (por ejemplo, desarrollo frente a producción secretos) en el mismo almacén. Se
recomienda que diferentes aplicaciones y entornos de desarrollo o de producción usan almacenes claves independientes
para aislar los entornos de aplicación para el nivel más alto de seguridad.
Con la segunda aplicación de ejemplo, crear un secreto en el almacén de claves para 5000-AppSecret (períodos
no están permitidos en los nombres de secreto de almacén de claves) que representa un secreto de la aplicación
para la versión 5.0.0.0 de la aplicación. Para obtener otra versión, 5.1.0.0, crear un secreto para 5100-AppSecret .
Cada versión de la aplicación carga su propio valor secreto en su configuración como AppSecret , extracción
desactivar la versión cuando se cargue el secreto. Implementación del ejemplo se muestra a continuación:
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
// The appVersion obtains the app version (5.0.0.0), which
// is set in the project file and obtained from the entry
// assembly. The versionPrefix holds the version without
// dot notation for the PrefixKeyVaultSecretManager.
var appVersion = Assembly.GetEntryAssembly().GetName().Version.ToString();
var versionPrefix = appVersion.Replace(".", string.Empty);
keyVaultConfigBuilder.AddAzureKeyVault(
$"https://{builtConfig["Vault"]}.vault.azure.net/",
builtConfig["ClientId"],
builtConfig["ClientSecret"],
new PrefixKeyVaultSecretManager(versionPrefix));
config.AddConfiguration(keyVaultConfig);
})
.UseStartup<Startup>();
El Load método se llama mediante un algoritmo de proveedor que recorre en iteración los secretos del almacén
para buscar los que tienen el prefijo de la versión. Cuando se encuentra un prefijo de versión con Load , el
algoritmo utiliza el GetKey método para devolver el nombre de configuración del nombre del secreto. Quita el
prefijo de la versión del nombre del secreto y devuelve el resto del nombre del secreto para cargar en la
configuración de la aplicación de pares nombre / valor.
Al implementar este enfoque:
1. Se cargan los secretos del almacén de claves.
2. El secreto de cadena para 5000-AppSecret se encuentran coincidencias.
3. La versión 5000 (con el guión), se quitan el nombre de clave dejando AppSecret para cargar con el valor de
secreto en configuración de la aplicación.
NOTE
También puede proporcionar sus propios KeyVaultClient implementación AddAzureKeyVault . Proporciona a un cliente
personalizado le permite compartir una única instancia del cliente entre el proveedor de configuración y otras partes de la
aplicación.
1. Crear un almacén de claves y configurar Azure Active Directory (Azure AD ) para la aplicación siguiendo
las instrucciones de empezar a trabajar con el almacén de claves de Azure.
Agregar secretos en el almacén de claves mediante el módulo de PowerShell de almacén de claves
AzureRM disponibles desde el Galería de PowerShell, el API de REST del almacén de claves de Azure,
o la Portal de azure. Los secretos se crean como Manual o certificado secretos. Certificado secretos
están certificados para su uso por aplicaciones y servicios, pero no son compatibles con el proveedor
de configuración. Debe utilizar el Manual opción para crear los secretos de par nombre / valor para su
uso con el proveedor de configuración.
Usan valores jerárquicos (secciones de configuración) -- (dos guiones) como separador.
Cree dos Manual secretos con los siguientes pares de nombre y valor:
5000-AppSecret : 5.0.0.0_secret_value
5100-AppSecret : 5.1.0.0_secret_value
Registrar la aplicación de ejemplo con Azure Active Directory.
Autorizar la aplicación para tener acceso al almacén de claves. Cuando se usa el
Set-AzureRmKeyVaultAccessPolicy cmdlet de PowerShell para autorizar la aplicación para tener acceso
al almacén de claves, proporcionar List y Get acceso a los secretos con
-PermissionsToSecrets list,get .
2. Actualización de la aplicación appSettings.JSON que se archivo con los valores de Vault , ClientId , y
ClientSecret .
3. Ejecute la aplicación de ejemplo, que obtiene sus valores de configuración de IConfigurationRoot con el
mismo nombre que el nombre de secreto con prefijo. En este ejemplo, el prefijo es la versión de la
aplicación, que proporciona a los PrefixKeyVaultSecretManager cuando agrega el proveedor de
configuración de almacén de claves de Azure. El valor de AppSecret se obtiene con config["AppSecret"] .
La página Web generada por la aplicación muestra el valor cargado:
4. Cambiar la versión del ensamblado en el archivo de proyecto de aplicación 5.0.0.0 a 5.1.0.0 y vuelva a
ejecutar la aplicación. En esta ocasión, el valor de secreto devuelto es 5.1.0.0_secret_value . La página
Web generada por la aplicación muestra el valor cargado:
Controlar el acceso a la ClientSecret
Use la herramienta Administrador de secreto para mantener la ClientSecret fuera de su árbol de código fuente
del proyecto. Con el Administrador de secreto, para asocia los secretos de aplicación a un proyecto específico y
compartirlos en varios proyectos.
Al desarrollar una aplicación de .NET Framework en un entorno que admite certificados, puede autenticarse en
el almacén de claves de Azure con un certificado X.509. Clave privada del certificado X.509 es administrada por
el sistema operativo. Para obtener más información, consulte autenticar con un certificado en lugar de un
secreto de cliente. Use la AddAzureKeyVault sobrecarga que acepta un X509Certificate2 .
builder.AddAzureKeyVault(
config["Vault"],
config["ClientId"],
cert.OfType<X509Certificate2>().Single(),
new EnvironmentSecretManager(env.ApplicationName));
store.Close();
Configuration = builder.Build();
Configuration.Reload();
Solución de problemas
Cuando no se puede cargar la configuración mediante el proveedor de la aplicación, se escribe un mensaje de
error en la infraestructura del registro de ASP.NET. Las siguientes condiciones se evita que la configuración de
carga:
La aplicación no está configurada correctamente en Azure Active Directory.
El almacén de claves no existe en el almacén de claves de Azure.
La aplicación no está autorizada para tener acceso al almacén de claves.
No incluye la directiva de acceso Get y List permisos.
En el almacén de claves, los datos de configuración (par nombre / valor) se denominó incorrectamente, falta,
deshabilitado o expirado.
La aplicación tiene el nombre de almacén de claves incorrecto ( Vault ), Id. de aplicación de Azure AD (
ClientId ), o la clave de AD de Azure ( ClientSecret ).
La clave de AD de Azure ( ClientSecret ) ha expirado.
La clave de configuración (nombre) es incorrecta en la aplicación para el valor que está intentando cargar.
Recursos adicionales
Configuración
Microsoft Azure: Almacén de claves
Microsoft Azure: Documentación de almacén de claves
Cómo generar y transferir protegidas con HSM de claves para el almacén de claves de Azure
Clase KeyVaultClient
Ataques de evitar Cross-Site falsificación de
solicitud (XSRF/CSRF) en ASP.NET Core
22/06/2018 • 26 minutes to read • Edit Online
Tenga en cuenta que el formulario action entradas para el sitio sea vulnerable, no para el
sitio malintencionado. Esta es la parte "cross-site" de CSRF.
3. El usuario selecciona el botón Enviar. El explorador realiza la solicitud y automáticamente
incluye la cookie de autenticación para el dominio solicitado, www.good-banking-site.com .
4. La solicitud se ejecuta en el www.good-banking-site.com servidor con contexto de autenticación
del usuario y pueden realizar cualquier acción que puede realizar un usuario autenticado.
Además el escenario donde el usuario selecciona el botón para enviar el formulario, el sitio
malintencionado podría:
Ejecutar un script que envía automáticamente el formulario.
Envía el envío del formulario como una solicitud AJAX.
Ocultar el formulario de uso de CSS.
Estos escenarios alternativos no requieren ninguna acción o entrada del usuario que no sea
inicialmente visitar el sitio malintencionado.
Uso de HTTPS no impide que un ataque CSRF. El sitio malintencionado puede enviar un
https://www.good-banking-site.com/ solicitar de manera tan sencilla como puede enviar una solicitud
insegura.
Algunos ataques de destino que respondan a las solicitudes GET, en cuyo caso se puede utilizar una
etiqueta de imagen para realizar la acción. Esta forma de ataque es habitual en los sitios de foro que
permiten imágenes pero bloquean JavaScript. Las aplicaciones que cambian el estado de las
solicitudes GET, donde se modifican las variables o los recursos, son vulnerables a ataques
malintencionados. Las solicitudes GET que cambiarán el estado no son seguros. Una práctica
recomendada consiste en no cambiar nunca el estado en una solicitud GET.
Los ataques CSRF son posibles con aplicaciones web que usan cookies para la autenticación porque:
Los exploradores almacenan las cookies emitidas por una aplicación web.
Almacenado cookies incluyen cookies de sesión para los usuarios autenticados.
Exploradores envían que todas las cookies asociadas con un dominio a la aplicación web de todas
las solicitudes, independientemente de cómo se generó la solicitud de aplicación dentro del
explorador.
Sin embargo, no están limitados CSRF ataques para aprovecharse de las cookies. Por ejemplo, la
autenticación básica e implícita también son vulnerables. Una vez que un usuario inicia sesión con
autenticación básica o implícita, el explorador envía automáticamente las credenciales hasta que la
sesión† finaliza.
†En este contexto, sesión hace referencia a la sesión de cliente durante el cual el usuario está
autenticado. Es no relacionados con las sesiones de servidor o Middleware de sesión de ASP.NET
Core.
Los usuarios pueden protegerse contra vulnerabilidades CSRF, tome precauciones:
Inicie sesión en las aplicaciones web cuando termine de utilizarlos.
Borrar las cookies del explorador periódicamente.
Sin embargo, las vulnerabilidades CSRF son fundamentalmente un problema con la aplicación web,
no el usuario final.
<form method="post">
...
</form>
Se puede deshabilitar la generación automática de símbolos (tokens) antiforgery para los elementos
de formulario HTML:
Deshabilitar explícitamente antiforgery tokens con el asp-antiforgery atributo:
<form method="post" asp-antiforgery="false">
...
</form>
<!form method="post">
...
</!form>
@removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.FormTagHelper,
Microsoft.AspNetCore.Mvc.TagHelpers
NOTE
Las páginas de Razor están protegidos automáticamente frente a XSRF/CSRF. Para obtener más información,
consulte XSRF/CSRF y páginas de Razor.
El enfoque más común para defenderse contra ataques CSRF consiste en usar la patrón del Token
Sincronizador (STP ). STP se utiliza cuando el usuario solicita una página con datos del formulario:
1. El servidor envía un token asociado con la identidad del usuario actual al cliente.
2. El cliente devuelve el token en el servidor para la comprobación.
3. Si el servidor recibe un token que no coincide con la identidad del usuario autenticado, se rechaza
la solicitud.
El token es único y no previstos. El token puede usarse también para asegurarse de la secuencia
correcta de una serie de solicitudes (por ejemplo, garantizar la secuencia de solicitud de: página 1 –
página 2 – página 3). Todos los formularios en plantillas de MVC de ASP.NET Core y páginas de
Razor generan tokens antiforgery. El siguiente par de ejemplos de vista genera tokens antiforgery:
Agregar explícitamente un token antiforgery a una <form> elemento sin usar aplicaciones auxiliares
de etiquetas con la aplicación auxiliar HTML @Html.AntiForgeryToken :
En cada uno de los casos anteriores, ASP.NET Core agrega un campo de formulario oculto similar al
siguiente:
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8NrAkS ... s2-m9Yw">
ASP.NET Core incluye tres filtros para trabajar con tokens antiforgery:
ValidateAntiForgeryToken
AutoValidateAntiforgeryToken
IgnoreAntiforgeryToken
Opciones de antiforgery
Personalizar opciones antiforgery en Startup.ConfigureServices :
services.AddAntiforgery(options =>
{
options.CookieDomain = "contoso.com";
options.CookieName = "X-CSRF-TOKEN-COOKIENAME";
options.CookiePath = "Path";
options.FormFieldName = "AntiforgeryFieldname";
options.HeaderName = "X-CSRF-TOKEN-HEADERNAME";
options.RequireSsl = false;
options.SuppressXFrameOptionsHeader = false;
});
OPCIÓN DESCRIPCIÓN
if (
string.Equals(path, "/", StringComparison.OrdinalIgnoreCase) ||
string.Equals(path, "/index.html", StringComparison.OrdinalIgnoreCase))
{
// The request token can be sent as a JavaScript-readable cookie,
// and Angular uses it by default.
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,
new CookieOptions() { HttpOnly = false });
}
return next(context);
});
}
if (user != null)
{
var result =
await _userManager.RemoveLoginAsync(
user, account.LoginProvider, account.ProviderKey);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
message = ManageMessageId.RemoveLoginSuccess;
}
}
El ValidateAntiForgeryToken atributo requiere un token para las solicitudes a los métodos de acción
que decora, incluidas las solicitudes HTTP GET. Si el ValidateAntiForgeryToken atributo se aplica en
todos los controladores de la aplicación, puede reemplazarse por el IgnoreAntiforgeryToken atributo.
NOTE
ASP.NET Core no admite la adición de tokens antiforgery a solicitudes GET automáticamente.
Validar automáticamente los tokens antiforgery para solo los métodos HTTP no seguros
Las aplicaciones ASP.NET Core no generan antiforgery tokens para los métodos HTTP seguros (GET,
HEAD, opciones y seguimiento). En lugar de aplicar ampliamente el ValidateAntiForgeryToken
atributo y, a continuación, reemplazar con IgnoreAntiforgeryToken atributos, los
AutoValidateAntiforgeryToken atributo se puede usar. Este atributo funciona de forma idéntica a la
ValidateAntiForgeryToken de atributo, salvo que no requiere tokens para las solicitudes realizadas
con los siguientes métodos HTTP:
GET
HEAD
OPCIONES
TRACE
Se recomienda usar AutoValidateAntiforgeryToken ampliamente para escenarios de API no. Esto
garantiza que las acciones de entrada están protegidas de forma predeterminada. La alternativa es
omitir antiforgery símbolos (tokens) de forma predeterminada, a menos que
ValidateAntiForgeryToken se aplica a los métodos de acción individuales. No esté más probable es
que en este escenario para un método de acción POST que desee dejar protegida por error, salir de
la aplicación sea vulnerable a ataques CSRF. Todas las publicaciones deben enviar el token
antiforgery.
Las API no tienen un mecanismo automático para el envío de la parte no cookie del token. La
implementación probablemente depende de la implementación del código de cliente. A continuación
se muestran algunos ejemplos:
Ejemplo de nivel de clase:
[Authorize]
[AutoValidateAntiforgeryToken]
public class ManageController : Controller
{
Ejemplo global:
services.AddMvc(options =>
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()));
[Authorize]
[AutoValidateAntiforgeryToken]
public class ManageController : Controller
{
[HttpPost]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> DoSomethingSafe(SomeViewModel model)
{
// no antiforgery token required
}
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>
<div class="row">
<p><input type="button" id="antiforgery" value="Antiforgery"></p>
<script>
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (xhttp.readyState == XMLHttpRequest.DONE) {
if (xhttp.status == 200) {
alert(xhttp.responseText);
} else {
alert('There was an error processing the AJAX request.');
}
}
};
document.addEventListener('DOMContentLoaded', function() {
document.getElementById("antiforgery").onclick = function () {
xhttp.open('POST', '@Url.Action("Antiforgery", "Home")', true);
xhttp.setRequestHeader("RequestVerificationToken",
document.getElementById('RequestVerificationToken').value);
xhttp.send();
}
});
</script>
</div>
Este enfoque elimina la necesidad de tratar directamente con la configuración de cookies del
servidor o para leerlos desde el cliente.
El ejemplo anterior utiliza JavaScript para leer el valor del campo oculto para el encabezado de
POST de AJAX.
JavaScript puede tokens de acceso en las cookies y usar el contenido de la cookie para crear un
encabezado con el valor del token.
context.Response.Cookies.Append("CSRF-TOKEN", tokens.RequestToken,
new Microsoft.AspNetCore.Http.CookieOptions { HttpOnly = false });
Suponiendo que la secuencia de comandos las solicitudes para enviar el token en un encabezado
denominado X-CSRF-TOKEN , configurar el servicio antiforgery para buscar la X-CSRF-TOKEN
encabezado:
En el ejemplo siguiente se utiliza JavaScript para realizar una solicitud de AJAX con el encabezado
adecuado:
function getCookie(cname) {
var name = cname + "=";
var decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.split(';');
for(var i = 0; i <ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}
AngularJS
AngularJS usa una convención para dirección CSRF. Si el servidor envía una cookie con el nombre
XSRF-TOKEN , AngularJS $http servicio agrega el valor de la cookie a un encabezado cuando envía
una solicitud al servidor. Este proceso es automático. El encabezado no tiene que establecerse de
forma explícita. El nombre de encabezado es X-XSRF-TOKEN . El servidor debe detectar este
encabezado y validar su contenido.
Para la API de ASP.NET básica trabajar con esta convención:
Configurar la aplicación para proporcionar un token en una cookie denominada XSRF-TOKEN .
Configurar el servicio para que busque un encabezado denominado antiforgery X-XSRF-TOKEN .
Extender antiforgery
El IAntiForgeryAdditionalDataProvider tipo permite a los desarrolladores extender el
comportamiento del sistema anti-CSRF por datos adicionales de ida y vuelta de cada token. El
GetAdditionalData método se llama cada vez que se genera un token de campo y el valor devuelto
está incrustado en el token generado. Un implementador podría devolver una marca de tiempo, un
nonce o cualquier otro valor y, a continuación, llame a ValidateAdditionalData para validar estos
datos cuando se valida el token. Nombre de usuario del cliente ya está incrustada en los tokens
generados, por lo que no es necesario incluir esta información. Si un token incluye datos
suplementarios pero no IAntiForgeryAdditionalDataProvider está configurado, no validados los
datos suplementarios.
Recursos adicionales
CSRF en Abrir proyecto de seguridad de aplicación Web (OWASP ).
Evitar los ataques de redirección abierta en ASP.NET
Core
22/06/2018 • 6 minutes to read • Edit Online
Una aplicación web que redirija a una dirección URL que se especifica a través de la solicitud como la cadena de
consulta o un formulario de datos potencialmente puede alterada para redirigir a los usuarios a una dirección URL
externa, malintencionada. Esta modificación se llama a un ataque de redirección abierta.
Cada vez que la lógica de aplicación se redirige a una dirección URL especificada, debe comprobar que la dirección
URL de redireccionamiento no se ha manipulado. ASP.NET Core tiene funcionalidad integrada para ayudar a
proteger las aplicaciones frente a ataques de redirección abierta (también conocido como abrir redirección).
LocalRedirect se iniciará una excepción si se especifica una dirección URL no locales. En caso contrario, se
comporta igual que el Redirect método.
IsLocalUrl
Use la IsLocalUrl método para probar las direcciones URL antes de redirigir:
En el ejemplo siguiente se muestra cómo comprobar si una dirección URL es local antes de redirigir.
El IsLocalUrl método protege a los usuarios sin darse cuenta sea redirigido a un sitio malintencionado. Puede
registrar los detalles de la dirección URL que se proporcionó cuando se proporciona una dirección URL no es local
en una situación donde se espera una dirección URL local. Registro de redirección de direcciones URL pueden
ayudar a diagnosticar los ataques de redirección.
Evitar que las secuencias de comandos (XSS) en
ASP.NET Core
22/06/2018 • 13 minutes to read • Edit Online
@{
var untrustedInput = "<\"123\">";
}
@untrustedInput
Esta vista muestra el contenido de la untrustedInput variable. Esta variable incluye algunos caracteres que se usan
en los ataques XSS, es decir, <, "y >. Examinar el código fuente, muestra el resultado representado codificado
como:
<"123">
WARNING
Núcleo ASP.NET MVC proporciona una HtmlString clase que no está codificado automáticamente tras la salida. Esto
nunca debe utilizarse en combinación con la entrada no es de confianza como esto expondrá una vulnerabilidad XSS.
@{
var untrustedInput = "<\"123\">";
}
<div
id="injectedData"
data-untrustedinput="@untrustedInput" />
<script>
var injectedData = document.getElementById("injectedData");
// All clients
var clientSideUntrustedInputOldStyle =
injectedData.getAttribute("data-untrustedinput");
document.write(clientSideUntrustedInputOldStyle);
document.write("<br />")
document.write(clientSideUntrustedInputHtml5);
</script>
<script>
var injectedData = document.getElementById("injectedData");
var clientSideUntrustedInputOldStyle =
injectedData.getAttribute("data-untrustedinput");
var clientSideUntrustedInputHtml5 =
injectedData.dataset.untrustedinput;
document.write(clientSideUntrustedInputOldStyle);
document.write("<br />")
document.write(clientSideUntrustedInputHtml5);
</script>
<"123">
<"123">
@using System.Text.Encodings.Web;
@inject JavaScriptEncoder encoder;
@{
var untrustedInput = "<\"123\">";
}
<script>
document.write("@encoder.Encode(untrustedInput)");
</script>
<script>
document.write("\u003C\u0022123\u0022\u003E");
</script>
WARNING
No concatene que no se confía en JavaScript para crear elementos DOM. Debe usar createElement() y asignar valores de
propiedad correctamente como node.TextContent= , o use element.SetAttribute() / element[attribute]= en caso
contrario, se expone a DOM-based XSS.
WARNING
No use la entrada no es de confianza como parte de una ruta de acceso de dirección URL. Siempre pase datos
proporcionados no es de confianza como un valor de cadena de consulta.
Al ver el origen de la página web verá que se ha representado como sigue, con el texto en chino codificado;
Para ampliar los caracteres que se tratan como seguro para el codificador se insertaría la línea siguiente en el
ConfigureServices() método startup.cs ;
services.AddSingleton<HtmlEncoder>(
HtmlEncoder.Create(allowedRanges: new[] { UnicodeRanges.BasicLatin,
UnicodeRanges.CjkUnifiedIdeographs }));
En este ejemplo se amplía la lista segura para que incluya la CjkUnifiedIdeographs de intervalo de Unicode.
Ahora se convertiría en la salida representada
Intervalos de lista segura se especifican como gráficos de código Unicode, no los idiomas. El estándar Unicode
tiene una lista de gráficos de código puede usar para buscar el gráfico que contiene los caracteres. Cada
codificador, Html, JavaScript y dirección Url, debe configurarse por separado.
NOTE
Personalización de la lista segura sólo afecta a los codificadores de origen a través de DI. Si tiene acceso directamente a un
codificador a través de System.Text.Encodings.Web.*Encoder.Default , a continuación, el valor predeterminado, Latín
básico se usará sólo lista segura.
http://example.com/bar.html
NOTE
Internet Explorer no tiene en cuenta el puerto al comparar orígenes.
Configuración de CORS
Para configurar CORS en su aplicación, agregue el paquete Microsoft.AspNetCore.Cors al proyecto.
Agregue los servicios CORS en Startup.cs:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
Nota: Debe especificarse la dirección URL sin una barra diagonal final ( / ). Si la dirección URL finaliza con / ,
la comparación devolverá false y no se devolverá ningún encabezado.
La expresión lambda toma un objeto CorsPolicyBuilder . Encontrará una lista de las opciones de configuración
más adelante en este tema. En este ejemplo, la directiva permite las solicitudes entre orígenes de
http://example.com y no de otros orígenes.
Tenga en cuenta que CorsPolicyBuilder tiene una API fluida, por lo que se pueden encadenar llamadas de
método:
app.UseCors(builder =>
builder.WithOrigins("http://example.com")
.AllowAnyHeader()
);
El segundo enfoque es definir una o varias directivas CORS con nombre y, después, seleccionar la directiva por
su nombre en tiempo de ejecución.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOrigin",
builder => builder.WithOrigins("http://example.com"));
});
}
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
Este ejemplo agrega una directiva CORS denominada "AllowSpecificOrigin". Para seleccionar la directiva, pase el
nombre a UseCors .
[HttpGet]
[EnableCors("AllowSpecificOrigin")]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
[Route("api/[controller]")]
[EnableCors("AllowSpecificOrigin")]
public class ValuesController : Controller
Global
Puede habilitar CORS globalmente para todos los controladores si agrega el filtro
CorsAuthorizationFilterFactory a la colección de filtros globales:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.Configure<MvcOptions>(options =>
{
options.Filters.Add(new CorsAuthorizationFilterFactory("AllowSpecificOrigin"));
});
}
El orden de prioridad es: acción, controlador, global. Las directivas de nivel de acción tienen prioridad sobre las
directivas de nivel de controlador y estas últimas tienen prioridad sobre las directivas globales.
Deshabilitar CORS
Para deshabilitar CORS para una acción o un controlador, use el atributo [DisableCors] .
[HttpGet("{id}")]
[DisableCors]
public string Get(int id)
{
return "value";
}
options.AddPolicy("AllowSpecificOrigins",
builder =>
{
builder.WithOrigins("http://example.com", "http://www.contoso.com");
});
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace CorsExample4
{
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?
LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
// BEGIN01
options.AddPolicy("AllowSpecificOrigins",
builder =>
{
builder.WithOrigins("http://example.com", "http://www.contoso.com");
});
// END01
// BEGIN02
options.AddPolicy("AllowAllOrigins",
builder =>
{
builder.AllowAnyOrigin();
});
// END02
// BEGIN03
options.AddPolicy("AllowSpecificMethods",
builder =>
{
builder.WithOrigins("http://example.com")
.WithMethods("GET", "POST", "HEAD");
});
// END03
// BEGIN04
options.AddPolicy("AllowAllMethods",
builder =>
{
builder.WithOrigins("http://example.com")
.AllowAnyMethod();
});
// END04
// BEGIN05
options.AddPolicy("AllowHeaders",
builder =>
{
builder.WithOrigins("http://example.com")
.WithHeaders("accept", "content-type", "origin", "x-custom-header");
});
// END05
// BEGIN06
options.AddPolicy("AllowAllHeaders",
builder =>
{
builder.WithOrigins("http://example.com")
.AllowAnyHeader();
});
// END06
// BEGIN07
options.AddPolicy("ExposeResponseHeaders",
builder =>
{
builder.WithOrigins("http://example.com")
.WithExposedHeaders("x-custom-header");
});
// END07
// BEGIN08
options.AddPolicy("AllowCredentials",
builder =>
{
builder.WithOrigins("http://example.com")
.AllowCredentials();
});
// END08
// BEGIN09
options.AddPolicy("SetPreflightExpiration",
builder =>
{
builder.WithOrigins("http://example.com")
.SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
});
// END09
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors("AllowSpecificOrigins");
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World!");
});
}
}
}
Debe considerar detenidamente antes de permitir que las solicitudes de cualquier origen. Significa que
literalmente cualquier sitio Web puede realizar las llamadas de AJAX a su API.
Establecer los métodos HTTP permitidos
Para permitir todos los métodos HTTP:
options.AddPolicy("AllowAllMethods",
builder =>
{
builder.WithOrigins("http://example.com")
.AllowAnyMethod();
});
options.AddPolicy("AllowAllHeaders",
builder =>
{
builder.WithOrigins("http://example.com")
.AllowAnyHeader();
});
options.AddPolicy("ExposeResponseHeaders",
builder =>
{
builder.WithOrigins("http://example.com")
.WithExposedHeaders("x-custom-header");
});
En jQuery:
$.ajax({
type: 'get',
url: 'http://www.example.com/home',
xhrFields: {
withCredentials: true
}
Además, el servidor debe permitir las credenciales. Para permitir las credenciales entre orígenes:
options.AddPolicy("AllowCredentials",
builder =>
{
builder.WithOrigins("http://example.com")
.AllowCredentials();
});
Ahora, la respuesta HTTP incluirá un encabezado Access-Control-Allow -Credentials, que indica al explorador
que el servidor permite credenciales para una solicitud entre orígenes.
Si el explorador envía las credenciales, pero la respuesta no incluye un encabezado Access-Control-Allow -
Credentials válido, el explorador no expone la respuesta a la aplicación y se produce un error en la solicitud de
AJAX.
Tenga cuidado al permitir credenciales entre orígenes. Un sitio web en otro dominio puede enviar las
credenciales de un usuario que ha iniciado sesión a la aplicación en nombre del usuario sin su conocimiento. La
especificación de CORS también indica que configurar los orígenes en "*" (todos los orígenes) no es válido si
está presente el encabezado Access-Control-Allow-Credentials .
Establecer el tiempo de expiración de las comprobaciones preparatorias
El encabezado Access-Control-Max-Age especifica durante cuánto tiempo puede almacenarse en caché la
respuesta a la solicitud de comprobaciones preparatorias. Para establecer este encabezado:
options.AddPolicy("SetPreflightExpiration",
builder =>
{
builder.WithOrigins("http://example.com")
.SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
});
Funcionamiento de CORS
Esta sección describe lo que ocurre en una solicitud de CORS en el nivel de los mensajes HTTP. Es importante
comprender el funcionamiento de CORS para que se pueda configurar correctamente la directiva CORS y se
puedan solucionar los problemas cuando se produzcan comportamientos inesperados.
La especificación de CORS presenta varios encabezados HTTP nuevos que permiten las solicitudes entre
orígenes. Si un explorador es compatible con CORS, establece estos encabezados automáticamente para las
solicitudes entre orígenes. No se necesita código JavaScript personalizado para habilitar CORS.
Este es un ejemplo de una solicitud entre orígenes. El encabezado Origin proporciona el dominio del sitio que
está realizando la solicitud:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: text/plain; charset=utf-8
Access-Control-Allow-Origin: http://myclient.azurewebsites.net
Date: Wed, 20 May 2015 06:27:30 GMT
Content-Length: 12
Test message
La solicitud preparatoria utiliza el método HTTP OPTIONS. Incluye dos encabezados especiales:
Access-Control-Request-Method: el método HTTP que se usará para la solicitud real.
Access-Control-Request-Headers: una lista de encabezados de solicitud que la aplicación establece en la
solicitud real. (De nuevo, esto no incluye los encabezados que establece el explorador).
Esta es una respuesta de ejemplo, suponiendo que el servidor permite que la solicitud:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 0
Access-Control-Allow-Origin: http://myclient.azurewebsites.net
Access-Control-Allow-Headers: x-my-custom-header
Access-Control-Allow-Methods: PUT
Date: Wed, 20 May 2015 06:33:22 GMT
La respuesta incluye un encabezado Access-Control-Allow -Methods que enumera los métodos permitidos y,
opcionalmente, un encabezado Access-Control-Allow -Headers, que muestra los encabezados permitidos. Si la
solicitud de comprobaciones preparatorias se realiza correctamente, el explorador envía la solicitud real, como se
ha descrito anteriormente.
Compartir cookies entre aplicaciones con ASP.NET y
ASP.NET Core
22/06/2018 • 10 minutes to read • Edit Online
services.AddDataProtection()
.PersistKeysToFileSystem(GetKeyRingDirInfo())
.SetApplicationName("SharedCookieApp");
services.ConfigureApplicationCookie(options => {
options.Cookie.Name = ".AspNet.SharedCookie";
});
Las claves de protección de datos y el nombre de la aplicación deben compartirse entre aplicaciones. En las
aplicaciones de ejemplo, GetKeyRingDirInfo devuelve la ubicación de almacenamiento de claves comunes para la
PersistKeysToFileSystem método. Use SetApplicationName para configurar un nombre de aplicación compartido
común ( SharedCookieApp en el ejemplo). Para obtener más información, consulte configurar la protección de datos.
Consulte la CookieAuthWithIdentity.Core del proyecto en el código de ejemplo (cómo descargar).
Al utilizar cookies directamente:
ASP.NET Core 2.x
ASP.NET Core 1.x
services.AddDataProtection()
.PersistKeysToFileSystem(GetKeyRingDirInfo())
.SetApplicationName("SharedCookieApp");
services.AddAuthentication("Identity.Application")
.AddCookie("Identity.Application", options =>
{
options.Cookie.Name = ".AspNet.SharedCookie";
});
Las claves de protección de datos y el nombre de la aplicación deben compartirse entre aplicaciones. En las
aplicaciones de ejemplo, GetKeyRingDirInfo devuelve la ubicación de almacenamiento de claves comunes para la
PersistKeysToFileSystem método. Use SetApplicationName para configurar un nombre de aplicación compartido
común ( SharedCookieApp en el ejemplo). Para obtener más información, consulte configurar la protección de datos.
Consulte la CookieAuth.Core del proyecto en el código de ejemplo (cómo descargar).
Models/IdentityModels.cs:
Usar IMemoryCache
Almacenamiento en caché en memoria es un servicio que se hace referencia desde la aplicación con inyección
de dependencia. Llame a AddMemoryCache en ConfigureServices :
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
@model DateTime?
<div>
<h2>Actions</h2>
<ul>
<li><a asp-controller="Home" asp-action="CacheTryGetValueSet">TryGetValue and Set</a></li>
<li><a asp-controller="Home" asp-action="CacheGet">Get</a></li>
<li><a asp-controller="Home" asp-action="CacheGetOrCreate">GetOrCreate</a></li>
<li><a asp-controller="Home" asp-action="CacheGetOrCreateAsync">GetOrCreateAsync</a></li>
<li><a asp-controller="Home" asp-action="CacheRemove">Remove</a></li>
</ul>
</div>
Las almacenadas en caché DateTime valor permanece en la memoria caché mientras haya solicitudes dentro
del tiempo de espera (y ningún expulsión debido a la presión de memoria). La siguiente imagen muestra la
hora actual y una hora anterior recuperadas desde la caché:
Vea IMemoryCache métodos y CacheExtensions métodos para obtener una descripción de los métodos de la
memoria caché.
Usar MemoryCacheEntryOptions
El ejemplo siguiente:
Establece la hora de expiración absoluta. Esto es el tiempo máximo que puede almacenarse en caché la
entrada y evita que el elemento se conviertan en obsoletos cuando continuamente se renueva el plazo de
caducidad.
Establece un tiempo de expiración variable. Las solicitudes que tienen acceso a este elemento almacenado
en caché, restablecerán el reloj de expiración deslizante.
Establece la prioridad de la memoria caché en CacheItemPriority.NeverRemove .
Establece un PostEvictionDelegate al que se llama después de la entrada se expulsa de la memoria caché. La
devolución de llamada se ejecuta en un subproceso diferente del código que se quita el elemento de la
memoria caché.
public IActionResult CreateCallbackEntry()
{
var cacheEntryOptions = new MemoryCacheEntryOptions()
// Pin to cache.
.SetPriority(CacheItemPriority.NeverRemove)
// Add eviction callback
.RegisterPostEvictionCallback(callback: EvictionCallback, state: this);
return RedirectToAction("GetCallbackEntry");
}
Dependencias de caché
El ejemplo siguiente muestra cómo expirar una entrada de caché si expira una entrada dependiente. Un
CancellationChangeToken se agrega al elemento almacenado en caché. Cuando Cancel se llama en el
CancellationTokenSource , ambas entradas de caché se desalojan.
public IActionResult CreateDependentEntries()
{
var cts = new CancellationTokenSource();
_cache.Set(CacheKeys.DependentCTS, cts);
_cache.Set(CacheKeys.Child,
DateTime.Now,
new CancellationChangeToken(cts.Token));
}
return RedirectToAction("GetDependentEntries");
}
Mediante un CancellationTokenSource permite varias entradas de caché que se expulsen como un grupo. Con
el using patrón en el código anterior, las entradas de caché se crean dentro de la using bloque heredarán
opciones de expiración y desencadenadores.
Notas adicionales
Cuando se usa una devolución de llamada para rellenar un elemento de caché:
Varias solicitudes pueden encontrar el valor de clave almacenada en caché vacía porque no se ha
completado la devolución de llamada.
Esto puede dar lugar a que varios subprocesos al rellenar el elemento en caché.
Cuando una entrada de caché se utiliza para crear otro, el elemento secundario copia tokens de
expiración y la configuración de expiración basada en el momento de la entrada primaria. El elemento
secundario no está expirada para eliminación manual o actualización de la entrada primaria.
Recursos adicionales
Trabajar con una memoria caché distribuida
Detectar cambios con tokens de cambio
Almacenamiento en caché de respuestas
Middleware de almacenamiento en caché de respuestas
Aplicación auxiliar de etiquetas de caché
Aplicación auxiliar de etiquetas de caché distribuida
Trabajar con una memoria caché distribuida en
ASP.NET Core
06/06/2018 • 11 minutes to read • Edit Online
NOTE
Si usa una memoria caché distribuida de SQL Server, algunas de las siguientes ventajas solo son ciertas si se utiliza una
instancia de base de datos independiente para la memoria caché que para los datos de origen de la aplicación.
Al igual que cualquier memoria caché, una memoria caché distribuida puede mejorar considerablemente la
capacidad de respuesta de una aplicación, ya que normalmente se pueden recuperar datos de la memoria
caché mucho más rápida que de una base de datos relacional (o servicio web).
La configuración de la caché es específica de la implementación. En este artículo se describe cómo configurar
los almacenes distribuidos en caché Redis y SQL Server. Independientemente de qué implementación se
seleccione, la aplicación interactúa con la memoria caché utilizando una interfaz común IDistributedCache .
La interfaz de IDistributedCache
El IDistributedCache interfaz incluye métodos sincrónicos y asincrónicos. La interfaz permite elementos
agregar, recuperar y quitar de la implementación de caché distribuida. El IDistributedCache interfaz incluye los
métodos siguientes:
Get, GetAsync
Toma una clave de cadena y recupera un elemento almacenado en caché como un byte[] si se encuentra en la
memoria caché.
Conjunto, SetAsync
Agrega un elemento (como byte[] ) a la memoria caché utilizando una clave de cadena.
Actualización de RefreshAsync
Actualiza un elemento en la memoria caché basado en su clave, restablecer su tiempo de espera de expiración
deslizante (si existe).
Quitar, aplica removeasync a
Quita una entrada de caché basada en su clave.
Para usar el IDistributedCache interfaz:
1. Agregue los paquetes de NuGet necesarios para el archivo de proyecto.
2. Configurar la implementación específica de IDistributedCache en su Startup la clase
ConfigureServices método y lo agrega al contenedor no existe.
3. Desde la aplicación Middleware o las clases de controlador MVC, solicite una instancia de
IDistributedCache desde el constructor. La instancia se proporcionarán con inyección de dependencia
(DI).
NOTE
No es necesario usar una duración de Singleton o en el ámbito para IDistributedCache instancias (al menos para las
implementaciones integradas). También puede crear una instancia donde podría necesitar uno (en lugar de usar inyección
de dependencia), pero esto puede dificultar el código probar e infringe el principio de dependencias explícitas.
namespace DistCacheSample
{
public class StartTimeHeader
{
private readonly RequestDelegate _next;
private readonly IDistributedCache _cache;
httpContext.Response.Headers.Append(
"Last-Server-Start-Time", startTimeString);
await _next.Invoke(httpContext);
}
}
En el código anterior, el valor almacenado en caché se lee pero nunca se ha escrito. En este ejemplo, el valor
sólo se establece cuando se inicia un servidor y no cambia. En un escenario de varios servidores, el servidor
más reciente para iniciar sobrescribirá los valores anteriores que se establecieron con otros servidores. El Get
y Set métodos utilizan el byte[] tipo. Por lo tanto, se debe convertir el valor de cadena mediante
Encoding.UTF8.GetString (para Get ) y Encoding.UTF8.GetBytes (para Set ).
NOTE
Puesto que IDistributedCacheestá configurado en el ConfigureServices método, está disponible para el
Configure método como parámetro. Agregado como un parámetro permitirá proporcionar la instancia configurada a
través de DI.
NOTE
Para instalar Redis en el equipo local, instale el paquete chocolatey https://chocolatey.org/packages/redis-64/ y ejecutar
redis-server desde un símbolo del sistema.
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.Extensions.Caching.SqlConfig.Tools"
Version="2.0.2" />
</ItemGroup>
Probar SqlConfig.Tools, ejecute el siguiente comando:
Al igual que todas las implementaciones de caché, la aplicación debe obtener y establecer valores de caché
mediante una instancia de IDistributedCache , no un SqlServerCache . El ejemplo implementa SqlServerCache
en el entorno de producción (por lo que se configura en ConfigureProductionServices ).
NOTE
El ConnectionString (y, opcionalmente, SchemaName y TableName ) normalmente deberían almacenarse fuera de
control de código fuente (por ejemplo, UserSecrets), ya que pueden contener las credenciales.
Recomendaciones
La hora de decidir qué implementación de IDistributedCache es adecuado para la aplicación, elija entre Redis
y SQL Server se basa en la infraestructura existente y entorno, los requisitos de rendimiento y experiencia de
su equipo. Si su equipo es más fácil trabajar con Redis, es una excelente opción. Si el equipo prefiera SQL
Server, puede estar seguro de que así la implementación. Tenga en cuenta que una solución de
almacenamiento en caché tradicional almacena datos en memoria que permite la recuperación rápida de datos.
Debe almacenar los datos de uso frecuente en una memoria caché y almacenar todos los datos en un almacén
persistente de back-end como SQL Server o el almacenamiento de Azure. Caché en Redis es una solución de
almacenamiento en caché que proporciona un alto rendimiento y baja latencia en comparación con la memoria
caché de SQL.
Recursos adicionales
Caché en Azure en Redis
Base de datos SQL en Azure
Almacenamiento en caché en memoria
Detectar cambios con tokens de cambio
Almacenamiento en caché de respuestas
Middleware de almacenamiento en caché de respuestas
Aplicación auxiliar de etiquetas de caché
Aplicación auxiliar de etiquetas de caché distribuida
Las respuestas en caché de ASP.NET Core
06/06/2018 • 16 minutes to read • Edit Online
NOTE
Las respuestas en caché en las páginas de Razor está disponible en ASP.NET Core 2.1 o posterior.
DIRECTIVA ACCIÓN
sin caché En las solicitudes: una memoria caché no debe usar una
respuesta almacenada para satisfacer la solicitud. Nota: El
servidor de origen vuelve a genera la respuesta para el
cliente y el software intermedio actualiza la respuesta
almacenada en la memoria caché.
Otros encabezados de caché que desempeñan un papel en el almacenamiento en caché se muestran en la tabla
siguiente.
HEADER FUNCIÓN
Atributo ResponseCache
El ResponseCacheAttribute especifica los parámetros necesarios para establecer encabezados apropiados en las
respuestas en caché.
WARNING
Deshabilitar el almacenamiento en caché para el contenido que contiene información para clientes autenticados. Sólo debe
habilitarse el almacenamiento en caché para el contenido que no cambia en función de la identidad de un usuario o si un
usuario ha iniciado sesión.
VaryByQueryKeys varía la respuesta almacenada en los valores de la lista de claves de consulta determinada.
Cuando un valor único de * es siempre el middleware varía las respuestas por todos los parámetros de cadena
de consulta de solicitud. VaryByQueryKeys requiere ASP.NET Core 1.1 o posterior.
El Middleware de almacenamiento en caché de la respuesta debe estar habilitado para establecer el
VaryByQueryKeys propiedad; en caso contrario, se produce una excepción en tiempo de ejecución. No hay un
encabezado HTTP correspondiente para el VaryByQueryKeys propiedad. La propiedad es una característica HTTP
controlada el Middleware de almacenamiento en caché de respuesta. Para que el middleware atender una
respuesta almacenada en caché, la cadena de consulta y el valor de cadena de consulta deben coincidir con una
solicitud anterior. Por ejemplo, considere la posibilidad de la secuencia de las solicitudes y resultados que se
muestran en la tabla siguiente.
SOLICITUD RESULTADO
La primera solicitud es devuelto por el servidor y en memoria caché de middleware. Dado que la cadena de
consulta coincide con la solicitud anterior, se devuelve la segunda solicitud middleware. La tercera solicitud no se
encuentra en la caché de middleware porque el valor de cadena de consulta no coincide con una solicitud
anterior.
El ResponseCacheAttribute se utiliza para crear y configurar (a través de IFilterFactory ) un
ResponseCacheFilter. El ResponseCacheFilter realiza el trabajo de actualización de los encabezados HTTP
adecuados y características de la respuesta. El filtro:
Quita los encabezados existentes para Vary , Cache-Control , y Pragma .
Escribe los encabezados adecuados en función de las propiedades establecidas en el ResponseCacheAttribute .
Actualiza la respuesta si el almacenamiento en caché característica HTTP VaryByQueryKeys se establece.
Variar
Este encabezado solo cuando se escribe el VaryByHeader se establece la propiedad. Se establece en el Vary valor
de la propiedad. El siguiente ejemplo se utiliza la VaryByHeader propiedad:
[!code-csharp]
[!code-csharp]
Puede ver los encabezados de respuesta con las herramientas de red de su explorador. La siguiente imagen
muestra la F12 de borde de salida en el red ficha cuando el About2 se actualiza el método de acción:
NoStore y Location.None
NoStore invalida la mayoría de las demás propiedades. Cuando esta propiedad se establece en true ,
Cache-Control encabezado se establece en no-store . Si Location está establecido en None :
El valor de Cache-Control está establecido en no-store,no-cache .
El valor de Pragma está establecido en no-cache .
Cache-Control: no-store,no-cache
Pragma: no-cache
Ubicación y la duración
Para habilitar el almacenamiento en caché, Duration debe establecerse en un valor positivo y Location debe ser
Any (valor predeterminado) o Client . En este caso, el Cache-Control encabezado se establece en el valor de
ubicación seguido por el max-age de la respuesta.
NOTE
Location de opciones de Any y Client traducir Cache-Control valores de encabezado public y private ,
respectivamente. Como se indicó anteriormente, establecer Location a None establece ambas Cache-Control y
Pragma encabezados para no-cache .
A continuación se muestra un ejemplo que muestra los encabezados genera estableciendo Duration y deja el
valor predeterminado Location valor:
[!code-csharp]
[!code-csharp]
Esto produce el siguiente encabezado:
Cache-Control: public,max-age=60
Cache-Control: public,max-age=60
Recursos adicionales
Almacenar las respuestas en las cachés
Control de caché
Almacenamiento en caché en memoria
Trabajar con una memoria caché distribuida
Detectar cambios con tokens de cambio
Middleware de almacenamiento en caché de respuestas
Aplicación auxiliar de etiquetas de caché
Aplicación auxiliar de etiquetas de caché distribuida
Respuesta de almacenamiento en caché de
Middleware en ASP.NET Core
14/06/2018 • 14 minutes to read • Edit Online
Package
Para incluir el software intermedio en el proyecto, agregue una referencia a la
Microsoft.AspNetCore.ResponseCaching empaquetar o usar el Microsoft.AspNetCore.App metapackage, que
está disponible para su uso en ASP. Núcleo NET 2.1 o posterior.
Configuración
En ConfigureServices , agregue el middleware a la colección de servicio.
services.AddResponseCaching();
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
Configurar la aplicación para usar el middleware con el UseResponseCaching método de extensión, que agrega
el middleware a la canalización de procesamiento de la solicitud. La aplicación de ejemplo agrega un
Cache-Control encabezado a la respuesta que se almacena en caché las respuestas almacenable en caché
durante 10 segundos. El ejemplo envía una Vary encabezado para configurar el middleware para dar servicio
a un respuesta almacenada en caché solo si la Accept-Encoding coincide con el encabezado de las solicitudes
posteriores de la solicitud original. En el ejemplo de código siguiente, CacheControlHeaderValue y
HeaderNames requieren un using instrucción para el Microsoft.Net.Http.Headers espacio de nombres.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseResponseCaching();
await next();
});
app.UseMvc();
}
Middleware de almacenamiento en caché de respuesta sólo almacena en caché las respuestas del servidor que
dan como resultado un código de estado 200 (OK). Otras respuestas, incluidos los páginas de errores, se
omiten el middleware.
WARNING
Las respuestas que incluye contenido para clientes autenticados deben marcarse como no almacenable en caché para
evitar que el middleware de almacenamiento y envío de las respuestas. Vea condiciones para almacenar en caché para
obtener más información acerca de cómo el middleware determina si es almacenable en caché una respuesta.
Opciones
El middleware ofrece tres opciones para controlar las respuestas en caché.
OPCIÓN DESCRIPCIÓN
services.AddResponseCaching(options =>
{
options.UseCaseSensitivePaths = true;
options.MaximumBodySize = 1024;
});
VaryByQueryKeys
Al utilizar controladores de MVC o Web API o modelos de página de las páginas de Razor, el ResponseCache
atributo especifica los parámetros necesarios para establecer los encabezados adecuados para las respuestas
en caché. El único parámetro de la ResponseCache atributo que estrictamente requiere el software intermedio es
VaryByQueryKeys , que no se corresponde con un encabezado HTTP real. Para obtener más información,
consulte ResponseCache atributo.
Si no usa el ResponseCache las respuestas en caché pueden variar atributo, con el VaryByQueryKeys
característica. Use la ResponseCachingFeature directamente desde el IFeatureCollection de la HttpContext :
Usa un solo valor igual a * en VaryByQueryKeys varía en la memoria caché por todos los parámetros de
consulta de solicitud.
HEADER DETALLES
Solución de problemas
Si almacenamiento en caché de comportamiento no es como se esperaba, confirme que las respuestas son
almacenable en caché y puede que se va a obtener de la caché. Examine los encabezados de la solicitud
entrante y encabezados de salida de la respuesta. Habilitar registro para ayudar con la depuración.
Al probar y solucionar problemas de comportamiento de almacenamiento en caché, un explorador puede
establecer encabezados de solicitud que afectan al almacenamiento en caché de maneras no deseados. Por
ejemplo, puede establecer un explorador la Cache-Control encabezado a no-cache o max-age=0 al actualizar
una página. Las siguientes herramientas pueden establecer explícitamente los encabezados de solicitud y son
preferibles para las pruebas de almacenamiento en caché:
Fiddler
Postman
Condiciones para el almacenamiento en caché
La solicitud debe producir una respuesta del servidor con un código de estado 200 (OK).
El método de solicitud debe ser GET o HEAD.
Middleware de Terminal, como Middleware de archivos estáticos, no se debe procesar la respuesta antes de
Middleware de almacenamiento en caché de respuesta.
El Authorization encabezado no debe estar presente.
Cache-Control parámetros del encabezado deben ser válidos y debe marcarse la respuesta public y no
marcado como private .
El Pragma: no-cache encabezado no debe estar presente si el Cache-Control encabezado no está presente,
como la Cache-Control encabezado reemplaza la Pragma encabezado cuando está presente.
El Set-Cookie encabezado no debe estar presente.
Vary parámetros del encabezado deben ser válida y no es igual a * .
La Content-Length valor de encabezado (si establece) debe coincidir con el tamaño del cuerpo de respuesta.
El IHttpSendFileFeature no se utiliza.
La respuesta no debe ser obsoleta según lo especificado por el Expires encabezado y el max-age y
s-maxage directivas de caché.
Búfer de respuesta debe ser correcta, y el tamaño de la respuesta debe ser menor que el configurado o
default SizeLimit .
La respuesta debe ser almacenable en caché según el RFC 7234 especificaciones. Por ejemplo, el no-store
directiva no debe existir en los campos de encabezado de solicitud o respuesta. Vea sección 3: almacenar
respuestas en las cachés de RFC 7234 para obtener más información.
NOTE
El sistema Antiforgery para generar tokens seguros para evitar la falsificación de solicitud entre sitios (CSRF) ataques
conjuntos el Cache-Control y Pragma encabezados a no-cache para que no se almacenan en caché las respuestas.
Para obtener información acerca de cómo deshabilitar antiforgery tokens para los elementos de formulario HTML, vea
antiforgery configuración de ASP.NET Core.
Recursos adicionales
Inicio de aplicaciones
Middleware
Almacenamiento en caché en memoria
Trabajar con una memoria caché distribuida
Detectar cambios con tokens de cambio
Almacenamiento en caché de respuestas
Aplicación auxiliar de etiquetas de caché
Aplicación auxiliar de etiquetas de caché distribuida
Middleware de compresión de respuesta para
ASP.NET Core
22/06/2018 • 18 minutes to read • Edit Online
Compresión de respuesta
Por lo general, cualquier respuesta que comprimen de forma nativa puede beneficiarse de compresión de
respuesta. Respuestas de forma no nativa comprimidas normalmente son: CSS, JavaScript, HTML, XML y JSON.
No debe comprimir activos comprimidos de forma nativa, como archivos PNG. Si se intentan volver a comprimir
aún más una respuesta cifrada de forma nativa, cualquier pequeña reducción adicional en tiempo de tamaño y la
transmisión probablemente resultar mínimo comparado con el tiempo que tardó en procesarse la compresión.
No comprimir los archivos de menos de aproximadamente 150-1000 bytes (según el contenido del archivo y la
eficacia de compresión). La sobrecarga de la compresión de archivos pequeños puede generar un archivo
comprimido mayor que el archivo sin comprimir.
Cuando un cliente puede procesar contenido comprimido, el cliente debe informar al servidor de sus capacidades
mediante el envío de la Accept-Encoding encabezado con la solicitud. Cuando un servidor envía contenido
comprimido, debe incluir información de la Content-Encoding encabezado en cómo se codifican las respuestas
comprimidas. Contenido designaciones de codificación admitidas por el middleware se muestran en la tabla
siguiente.
Para obtener más información, consulte el lista de codificación oficial contenido de IANA.
El middleware le permite agregar proveedores de compresión adicional para personalizado Accept-Encoding
valores de encabezado. Para obtener más información, consulte proveedores personalizados a continuación.
El software intermedio es capaz de reaccionar ante el valor de calidad (qvalue, q ) ponderación cuando enviada
por el cliente para dar prioridad a los esquemas de compresión. Para obtener más información, consulte RFC
7231: codificación aceptada.
Algoritmos de compresión se están sujetas a un equilibrio entre la velocidad de compresión y la eficacia de la
compresión. Eficacia en este contexto hace referencia al tamaño de la salida después de la compresión. El tamaño
más pequeño se logra mediante la mayoría óptimo compresión.
Los encabezados implicados en la solicitud, enviar, almacenamiento en caché y recibir contenido comprimido se
describen en la tabla siguiente.
HEADER ROL
Puede explorar las características del Middleware de compresión de respuesta con el aplicación de ejemplo. El
ejemplo muestra:
La compresión de las respuestas de aplicación mediante gzip y proveedores de compresión personalizada.
Cómo agregar un tipo MIME a la lista predeterminada de tipos MIME para la compresión.
Package
Para incluir el software intermedio en el proyecto, agregue una referencia a la
Microsoft.AspNetCore.ResponseCompression paquete. Esta característica está disponible para aplicaciones que
tienen como destino ASP.NET Core 1.1 o posterior.
Para incluir el software intermedio en el proyecto, agregue una referencia a la
Microsoft.AspNetCore.ResponseCompression empaquetar o usar el Microsoft.AspNetCore.App metapackage
(ASP.NET Core 2.1 o posterior).
Configuración
El código siguiente muestra cómo habilitar el Middleware de compresión de respuesta con la compresión de gzip
predeterminada y para los tipos MIME predeterminado.
NOTE
Utilice una herramienta como Fiddler, Firebug, o Postman para establecer el Accept-Encoding encabezado de solicitud y
estudiar los encabezados de respuesta, el tamaño y el cuerpo.
Enviar una solicitud a la aplicación de ejemplo sin la Accept-Encoding encabezado y observe que la respuesta es
sin comprimir. El Content-Encoding y Vary encabezados no están presentes en la respuesta.
Enviar una solicitud a la aplicación de ejemplo con el Accept-Encoding: gzip encabezado y observe que la
respuesta está comprimida. El Content-Encoding y Vary encabezados están presentes en la respuesta.
Proveedores
GzipCompressionProvider
Use la GzipCompressionProvider para comprimir las respuestas con gzip. Esto es el proveedor de compresión
predeterminado si no se especifica ninguno. Puede establecer la compresión de nivel con el
GzipCompressionProviderOptions.
El proveedor de compresión gzip usa de forma predeterminada el nivel de compresión más rápido
(CompressionLevel.Fastest), que no puede producir la compresión más eficaz. Si se desea la compresión más
eficaz, puede configurar el middleware de compresión óptima.
services.Configure<GzipCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Fastest;
});
}
tipos MIME
El middleware especifica un conjunto predeterminado de tipos MIME para la compresión:
text/plain
text/css
application/javascript
text/html
application/xml
text/xml
application/json
text/json
Puede reemplazar o anexar los tipos MIME con las opciones de Middleware de compresión de respuesta. Tenga
en cuenta que MIME de comodines tipos, como text/* no son compatibles. La aplicación de ejemplo agrega un
tipo MIME para image/svg+xml y comprime y sirve de imagen de titular de ASP.NET Core (banner.svg).
ASP.NET Core 2.x
ASP.NET Core 1.x
public void ConfigureServices(IServiceCollection services)
{
services.AddResponseCompression(options =>
{
options.Providers.Add<GzipCompressionProvider>();
options.Providers.Add<CustomCompressionProvider>();
options.MimeTypes =
ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "image/svg+xml" });
});
services.Configure<GzipCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Fastest;
});
}
Proveedores personalizados
Puede crear implementaciones de compresión personalizada con ICompressionProvider. El EncodingName
representa la codificación que el control de contenido ICompressionProvider genera. El middleware que usa esta
información para elegir el proveedor basándose en la lista especificada en el Accept-Encoding encabezado de la
solicitud.
Mediante la aplicación de ejemplo, el cliente envía una solicitud con el Accept-Encoding: mycustomcompression
encabezado. El software intermedio utiliza la implementación de compresión personalizada y devuelve la
respuesta con un Content-Encoding: mycustomcompression encabezado. El cliente debe poder descomprimir la
codificación personalizada en orden para una implementación de la compresión personalizado para que funcione.
ASP.NET Core 2.x
ASP.NET Core 1.x
services.Configure<GzipCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Fastest;
});
}
Solución de problemas
Utilice una herramienta como Fiddler, Firebug, o Postman, que permite establecer el Accept-Encoding
encabezado de solicitud y estudiar los encabezados de respuesta, el tamaño y el cuerpo. El Middleware de
compresión de respuesta comprime las respuestas que cumplen las condiciones siguientes:
El Accept-Encoding encabezado está presente con un valor de gzip , * , o codificación personalizada que
coincida con un proveedor de compresión personalizada que ha establecido. El valor no debe ser identity o
tener un valor de calidad (qvalue, q ) de 0 (cero).
El tipo MIME ( Content-Type ) debe establecerse y debe coincidir con un tipo MIME configurado en el
ResponseCompressionOptions.
La solicitud no debe incluir el Content-Range encabezado.
La solicitud debe utilizar inseguro protocolo (http), a menos que el protocolo seguro (https) se configura en las
opciones de Middleware de compresión de respuesta. Tenga en cuenta el peligro se ha descrito anteriormente
al habilitar la compresión de contenido segura.
Recursos adicionales
Inicio de aplicaciones
Middleware
Red de desarrollador de Mozilla: Codificación aceptada
RFC 7231 sección 3.1.2.1: Códigos contenidos
RFC 7230 sección 4.2.3: Codificación Gzip
Versión 4.3 de la especificación de formato del archivo GZIP
Migración a ASP.NET Core
10/05/2018 • 2 minutes to read • Edit Online
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<UserSecretsId>aspnet-{Project Name}-{GUID}</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.1.0"
PrivateAssets="All" />
</ItemGroup>
</Project>
La imagen anterior muestra la versión 2.0 con las eliminaciones en color rojo.
La siguiente imagen muestra el código 2.1. El código en verde sustituye a la versión 2.0:
namespace WebApp1
{
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace WebApp1
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
}
}
}
Cambios adicionales
SetCompatibilityVersion
Configuración de transporte
Migración de ASP.NET a ASP.NET Core
21/06/2018 • 13 minutes to read • Edit Online
Requisitos previos
.NET Core SDK 2.0 or later
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
</ItemGroup>
Cuando se usa el metapaquete, con la aplicación no se implementa ningún paquete al que se hace referencia en el
metapaquete. El almacén de tiempo de ejecución de .NET Core incluye estos activos que están compilados
previamente para mejorar el rendimiento. Vea Microsoft.AspNetCore.All metapackage for ASP.NET Core 2.x
(Metapaquete Microsoft.AspNetCore.All para ASP.NET Core 2.x) para obtener más detalles.
Este enfoque acopla la aplicación y el servidor en el que está implementada de forma que interfiere con la
implementación. En un esfuerzo por desacoplar, se introdujo OWIN para ofrecer una forma más limpia de usar
varios marcos de trabajo de manera conjunta. OWIN proporciona una canalización para agregar solo los módulos
necesarios. El entorno de hospedaje toma una función de inicio para configurar servicios y la canalización de
solicitud de la aplicación. Startup registra un conjunto de middleware en la aplicación. Para cada solicitud, la
aplicación llama a cada uno de los componentes de middleware con el puntero principal de una lista vinculada a un
conjunto de controladores existente. Cada componente de middleware puede agregar uno o varios controladores a
la canalización de control de la solicitud. Esto se consigue mediante la devolución de una referencia al controlador
que ahora es el primero de la lista. Cada controlador se encarga de recordar e invocar el controlador siguiente en
la lista. Con ASP.NET Core, el punto de entrada a una aplicación es Startup y ya no se tiene dependencia de
Global.asax. Cuando utilice OWIN con .NET Framework, use algo parecido a lo siguiente como canalización:
using Owin;
using System.Web.Http;
namespace WebApi
{
// Note: By default all requests go through this OWIN pipeline. Alternatively you can turn this off by
adding an appSetting owin:AutomaticAppStartup with value “false”.
// With this turned off you can still have OWIN apps listening on specific routes by adding routes in
global.asax file using MapOwinPath or MapOwinRoute extensions on RouteTable.Routes
public class Startup
{
// Invoked once at startup to configure your application.
public void Configuration(IAppBuilder builder)
{
HttpConfiguration config = new HttpConfiguration();
config.Routes.MapHttpRoute("Default", "{controller}/{customerID}", new { controller = "Customer",
customerID = RouteParameter.Optional });
config.Formatters.XmlFormatter.UseXmlSerializer = true;
config.Formatters.Remove(config.Formatters.JsonFormatter);
// config.Formatters.JsonFormatter.UseDataContractJsonSerializer = true;
builder.UseWebApi(config);
}
}
}
Esto configura las rutas predeterminadas y tiene como valor predeterminado XmlSerialization a través de Json.
Agregue otro middleware a esta canalización según sea necesario (carga de servicios, opciones de configuración,
archivos estáticos, etcétera).
ASP.NET Core usa un enfoque similar, pero no depende de OWIN para controlar la entrada. En lugar de eso, usa el
método Program.cs Main (similar a las aplicaciones de consola) y Startup se carga a través de ahí.
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
namespace WebApplication2
{
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
Startup debe incluir un método Configure . En Configure , agregue el middleware necesario a la canalización. En
el ejemplo siguiente (de la plantilla de sitio web predeterminada), se usan varios métodos de extensión para
configurar la canalización con compatibilidad para:
BrowserLink
Páginas de error
Archivos estáticos
ASP.NET Core MVC
identidad
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseIdentity();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
El host y la aplicación se han desacoplado, lo que proporciona la flexibilidad de pasar a una plataforma diferente en
el futuro.
NOTE
Para acceder a referencias más detalladas sobre el inicio de ASP.NET Core y middleware, consulte Inicio de la aplicación en
ASP.NET Core.
<appSettings>
<add key="UserName" value="User" />
<add key="Password" value="Password" />
</appSettings>
ASP.NET Core puede almacenar datos de configuración para la aplicación en cualquier archivo y cargarlos como
parte del arranque de middleware. El archivo predeterminado que se usa en las plantillas de proyecto es
appSettings.JSON:
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
// Here is where you can supply custom configuration settings, Since it is is JSON, everything is
represented as key: value pairs
// Name of section is your choice
"AppConfiguration": {
"UserName": "UserName",
"Password": "Password"
}
}
La carga de este archivo en una instancia de IConfiguration dentro de la aplicación se realiza en Startup.cs:
Existen extensiones de este enfoque para lograr que el proceso sea más sólido, como el uso de inserción de
dependencias para cargar un servicio con estos valores. El enfoque de la inserción de dependencias proporciona
un conjunto fuertemente tipado de objetos de configuración.
NOTE
Para acceder a referencias más detalladas sobre la configuración de ASP.NET Core, consulte Configuración en ASP.NET Core.
Dado que la inserción de dependencia forma parte de ASP.NET Core, puede agregar el servicio en el método
ConfigureServices de Startup.cs:
NOTE
Para acceder a referencias más detalladas sobre la inserción de dependencias en ASP.NET Core, consulte Inserción de
dependencias en ASP.NET Core.
Por ejemplo, el explorador puede acceder a un recurso de imagen en la carpeta wwwroot/images en una ubicación
como http://<app>/images/<imageFileName> .
NOTE
Para acceder a referencias más detalladas sobre cómo trabajar con archivos estáticos en ASP.NET Core, consulte Archivos
estáticos.
Recursos adicionales
Traslado a .NET Core: bibliotecas
Migrar de MVC de ASP.NET a ASP.NET Core MVC
22/06/2018 • 15 minutes to read • Edit Online
NOTE
Los números de versión en los ejemplos podrían no estar actualizados. Debe actualizar los proyectos en consecuencia.
namespace WebApp1
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit
https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request
pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
<h1>Hello world!</h1>
Ejecute la aplicación.
Controladores y vistas
Copiar cada uno de los métodos de ASP.NET MVC HomeController al nuevo HomeController . Tenga en
cuenta que en ASP.NET MVC, tipo de valor devuelto de método de acción de controlador de la plantilla
integrada ActionResult; en el núcleo de ASP.NET MVC, los métodos de acción devueltos IActionResult en
su lugar. ActionResult implementa IActionResult , por lo que no es necesario para cambiar el tipo de valor
devuelto de los métodos de acción.
Copia la About.cshtml, Contact.cshtml, y Index.cshtml archivos de vista Razor desde el proyecto de MVC de
ASP.NET para el proyecto de ASP.NET Core.
Ejecutar la aplicación de ASP.NET Core y cada método de prueba. No hemos migramos los estilos o el
archivo de diseño, por lo que las vistas representadas solo contienen el contenido de los archivos de vista.
No tendrá los vínculos de archivo generado de diseño para la About y Contact vistas, por lo que tendrá
que invocar a los mismos desde el explorador (reemplace 4492 con el número de puerto utilizado en el
proyecto).
http://localhost:4492/home/about
http://localhost:4492/home/contact
Tenga en cuenta la falta de un estilo y elementos de menú. Esto lo corregiremos en la sección siguiente.
Contenido estático
En versiones anteriores de ASP.NET MVC, contenido estático se alojó desde la raíz del proyecto web y se ha
combinado con archivos de servidor. En el núcleo de ASP.NET, contenido estático se hospeda en el wwwroot
carpeta. Desea copiar el contenido estático de la aplicación de MVC de ASP.NET anterior a la wwwroot carpeta en
el proyecto de ASP.NET Core. En esta conversión de ejemplo:
Copia la favicon.ico archivo desde el proyecto MVC anterior a la wwwroot carpeta del proyecto de ASP.NET
Core.
La antigua ASP.NET MVC project utiliza Bootstrap para su aplicación de estilos y almacena el arranque de los
archivos del contenido y Scripts carpetas. Hace referencia a la plantilla, que genera el proyecto de MVC de
ASP.NET anterior, arranque en el archivo de diseño (Views/Shared/_Layout.cshtml). Podría copiar el bootstrap.js y
bootstrap.css proyecto de archivos de ASP.NET MVC a la wwwroot carpeta en el nuevo proyecto. En su lugar,
vamos a agregar compatibilidad para el arranque (y otras bibliotecas de cliente) con CDN en la sección siguiente.
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
crossorigin="anonymous"></script>
@RenderSection("scripts", required: false)
</body>
</html>
Ver el sitio en el explorador. Ahora deberían cargarse correctamente, con los estilos esperados en su lugar.
Opcional: desea intentar usar el nuevo archivo de diseño. Para este proyecto se puede copiar el archivo de
diseño de la FullAspNetCore proyecto. El nuevo archivo de diseño usa aplicaciones auxiliares de etiquetas y
tiene otras mejoras.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
namespace WebApp1
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?
LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
ASP.NET Core convierte las excepciones no controladas en una aplicación web en las respuestas de error HTTP
500. Normalmente, los detalles del error no se incluyen en estas respuestas para evitar la divulgación de
información potencialmente confidencial acerca del servidor. Vea mediante la página de excepción para
desarrolladores en controlar los errores para obtener más información.
Recursos adicionales
Desarrollo del lado cliente
Aplicaciones auxiliares de etiquetas
Migrar de API Web de ASP.NET a ASP.NET Core
23/06/2018 • 14 minutes to read • Edit Online
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Routing;
namespace ProductsApp
{
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
}
}
}
namespace ProductsApp
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
Esta clase configura atributo enrutamiento, aunque realmente no se usa en el proyecto. También configura la tabla
de enrutamiento, que se usa por la API Web de ASP.NET. En este caso, ASP.NET Web API esperará direcciones
URL para que coincida con el formato /api/ {controller } / {id }, con {id } que es opcional.
El ProductsApp proyecto incluye un solo controlador simple, que hereda de ApiController y expone dos métodos:
using ProductsApp.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web.Http;
namespace ProductsApp.Controllers
{
public class ProductsController : ApiController
{
Product[] products = new Product[]
{
new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 },
new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M },
new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M }
};
Por último, el modelo, producto, utilizada por el ProductsApp, es una clase simple:
namespace ProductsApp.Models
{
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public decimal Price { get; set; }
}
}
Ahora que tenemos un proyecto simple desde el que se va a iniciar, podemos demostrar cómo migrar este
proyecto de API Web MVC de ASP.NET Core.
Eliminar el Project_Readme.html archivo desde el nuevo proyecto. La solución debe tener el siguiente aspecto:
Migrar la configuración
Ya no usa ASP.NET Core Global.asax, web.config, o App_Start carpetas. En su lugar, se realizan todas las tareas de
inicio en Startup.cs en la raíz del proyecto (vea inicio de la aplicación). En ASP.NET MVC de núcleo, el
enrutamiento basado en atributos ahora se incluye de forma predeterminada cuando UseMvc() se denomina; y, es
el enfoque recomendado para configurar las rutas de Web API (y es la forma en que el proyecto de inicio de la
API Web controla el enrutamiento).
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace ProductsCore
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseMvc();
}
}
}
Suponiendo que desea utilizar el enrutamiento de atributo en el proyecto en el futuro, se necesita ninguna
configuración adicional. Basta con aplicar los atributos según sea necesario para los controladores y acciones,
como se hace en el ejemplo ValuesController clase que se incluye en el proyecto de inicio de la API Web:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace ProductsCore.Controllers
{
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}
// POST api/values
[HttpPost]
public void Post([FromBody]string value)
{
}
// PUT api/values/5
[HttpPut("{id}")]
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/values/5
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
}
Tenga en cuenta la presencia de [controlador ] en la línea 8. Enrutamiento basado en atributos ahora es compatible
con determinados símbolos (tokens), como [controlador ] y [acción]. Estos tokens se reemplazan en tiempo de
ejecución con el nombre del controlador o acción, respectivamente, al que se ha aplicado el atributo. Esto sirve
para reducir el número de cadenas mágicos en el proyecto y garantiza que las rutas se mantendrá sincronizadas
con sus controladores correspondientes y las acciones cuando se aplican refactorizaciones rename automática.
Para migrar el controlador de API de productos, debemos primero copiaremos ProductsController al nuevo
proyecto. A continuación, basta con incluir el atributo de ruta en el controlador:
[Route("api/[controller]")]
También debe agregar el [HttpGet] atribuir a los dos métodos, ya que se deberían llamar a través de HTTP Get.
Incluir la expectativa de un parámetro "id" en el atributo para GetProduct() :
// /api/products
[HttpGet]
...
// /api/products/1
[HttpGet("{id}")]
En este momento, el enrutamiento está configurado correctamente; Sin embargo, aún no podemos probarlo. Se
deben realizar cambios adicionales antes de ProductsController se compilará.
namespace ProductsCore.Controllers
{
[Route("api/[controller]")]
public class ProductsController : Controller
{
Product[] products = new Product[]
{
new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 },
new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M },
new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M }
};
// /api/products
[HttpGet]
public IEnumerable<Product> GetAllProducts()
{
return products;
}
// /api/products/1
[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
var product = products.FirstOrDefault((p) => p.Id == id);
if (product == null)
{
return NotFound();
}
return Ok(product);
}
}
}
Ahora puede ejecutar el proyecto migrado y vaya a /api/productos; y, debería ver la lista completa de los 3
productos. Vaya a /api/products/1 y debería ver la primera el producto.
Microsoft.AspNetCore.Mvc.WebApiCompatShim
Es una herramienta muy útil cuando ASP.NET Web API migrar proyectos a ASP.NET Core el
Microsoft.AspNetCore.Mvc.WebApiCompatShim biblioteca. La corrección de compatibilidad extiende ASP.NET
Core para permitir a un número de distintas convenciones de Web API 2 para usarse. El ejemplo portar
anteriormente en este documento es bastante básico, por lo que la corrección de compatibilidad no era necesaria.
Para proyectos grandes, usando la corrección de compatibilidad puede ser útil para temporalmente tender API
entre ASP.NET Core y ASP.NET Web API 2.
La corrección de compatibilidad de API Web está pensada para usarse como una medida temporal para facilitar la
migración de grandes proyectos API Web a ASP.NET Core. Con el tiempo, los proyectos deben actualizarse para
usar patrones principales de ASP.NET en lugar de depender de la corrección de compatibilidad.
Entre las características de compatibilidad que se incluyen en Microsoft.AspNetCore.Mvc.WebApiCompatShim se
incluyen:
Agrega un ApiController tipo para que los tipos de base de controladores no tienen que actualizarse.
Habilita el enlace de modelo de estilo Web API. Funciones de enlace del modelo MVC ASP.NET Core de forma
similar a MVC 5, de forma predeterminada. Los cambios de la corrección de compatibilidad del modelo enlace
para que sea más similar a las convenciones de enlace de API Web 2 modelo. Por ejemplo, los tipos complejos
se enlazan automáticamente desde el cuerpo de solicitud.
Extiende el enlace de modelos para que las acciones de controlador pueden tomar parámetros de tipo
HttpRequestMessage .
Agrega los formateadores de mensajes que permite a las acciones para devolver los resultados de tipo
HttpResponseMessage .
Agrega los métodos de respuesta adicionales que pueden usado para atender las respuestas Web API 2
acciones:
Generadores de HttpResponseMessage:
CreateResponse<T>
CreateErrorResponse
Métodos de resultado de acción:
BadRequestErrorMessageResult
ExceptionResult
InternalServerErrorResult
InvalidModelStateResult
NegotiatedContentResult
ResponseMessageResult
Agrega una instancia de IContentNegotiator al contenedor de la aplicación DI y hace contenido tipos
relacionados con la negociación de Microsoft.AspNet.WebApi.Client disponibles. Esto incluye tipos como
DefaultContentNegotiator , MediaTypeFormatter , etcetera.
Resumen
Migrar un proyecto de ASP.NET Web API simple para MVC de ASP.NET Core es bastante sencilla, gracias a la
compatibilidad integrada con las API Web de MVC de ASP.NET Core. La principal que deben migrar todos los
proyectos de ASP.NET Web API son rutas, los controladores y los modelos, junto con las actualizaciones de los
tipos utilizados por los controladores y acciones.
Migrar la configuración a ASP.NET Core
22/06/2018 • 4 minutes to read • Edit Online
Parámetros de configuración
ASP.NET Core ya no utiliza la Global.asax y web.config archivos que utilizan versiones anteriores de ASP.NET. En
versiones anteriores de ASP.NET, la lógica de inicio de la aplicación se ha puesto en un Application_StartUp
método dentro de Global.asax. Más adelante, en ASP.NET MVC, un Startup.cs archivo se incluyó en la raíz del
proyecto; y, se llamó cuando se inició la aplicación. ASP.NET Core ha adoptado este enfoque por completo
mediante la colocación de toda la lógica de inicio en el Startup.cs archivo.
El web.config archivo también se ha sustituido en ASP.NET Core. Configuración en Sí ahora se puede configurar,
como parte del procedimiento de inicio de aplicación se describen en Startup.cs. Configuración todavía puede usar
los archivos XML, pero normalmente los proyectos de ASP.NET Core colocará los valores de configuración en un
archivo con formato JSON, como appSettings.JSON que se. Sistema de configuración de ASP.NET Core también
es posible tener acceso a las variables de entorno, lo que pueden proporcionar un ubicación más segura y sólida
para valores específicos del entorno. Esto es especialmente cierto para los secretos como cadenas de conexión y
las claves de API que no deben protegerse en el control de código fuente. Vea configuración para obtener más
información sobre la configuración de ASP.NET Core.
En este artículo, estamos comenzando con el proyecto de ASP.NET Core parcialmente migrado desde el artículo
anterior. Para la configuración de la instalación, agregue el siguiente constructor y la propiedad a la Startup.cs
archivo se encuentra en la raíz del proyecto:
Tenga en cuenta que en este momento, el Startup.cs archivo no se compilará, tal y como necesitamos agregar lo
siguiente using instrucción:
using Microsoft.Extensions.Configuration;
Agregar un appSettings.JSON que se archivo a la raíz del proyecto mediante la plantilla de elemento apropiado:
Migrar la configuración de web.config
Nuestro proyecto de ASP.NET MVC incluye la cadena de conexión de base de datos necesarios en web.config, en
la <connectionStrings> elemento. En el proyecto de ASP.NET Core, vamos a almacenar esta información en el
appSettings.JSON que se archivo. Abra appSettings.JSON que sey tenga en cuenta que ya incluye lo siguiente:
{
"Data": {
"DefaultConnection": {
"ConnectionString": "Server=(localdb)\\MSSQLLocalDB;Database=_CHANGE_ME;Trusted_Connection=True;"
}
}
}
Resumen
ASP.NET Core coloca toda la lógica de inicio de la aplicación en un único archivo, en el que los servicios necesarios
y las dependencias pueden definirse y configuradas. Reemplaza el web.config archivo con una característica de
configuración flexible que puede aprovechar una variedad de formatos de archivo, como JSON, así como las
variables de entorno.
Migrar de autenticación e identidad a ASP.NET Core
22/06/2018 • 5 minutes to read • Edit Online
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
}
En este momento, hay dos tipos que se hace referencia en el código anterior que hemos aún no hemos migrado
desde el proyecto de MVC de ASP.NET: ApplicationDbContext y ApplicationUser . Crear un nuevo modelos
carpeta en el núcleo de ASP.NET del proyecto y agregarle dos clases correspondientes a estos tipos. Encontrará
ASP.NET MVC versiones de estas clases en /Models/IdentityModels.cs, pero vamos a utilizar un archivo por cada
clase en el proyecto migrado ya que es más nítido.
ApplicationUser.cs:
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
namespace NewMvcProject.Models
{
public class ApplicationUser : IdentityUser
{
}
}
ApplicationDbContext.cs:
using Microsoft.AspNetCore.Identity.EntityFramework;
using Microsoft.Data.Entity;
namespace NewMvcProject.Models
{
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
El proyecto Web de inicio de MVC de ASP.NET Core no incluye la cantidad personalización de los usuarios, o la
ApplicationDbContext . Al migrar una aplicación real, también necesita migrar todas las propiedades
personalizadas y los métodos de usuario de la aplicación y DbContext clases, así como cualquier otra clase de
modelo que utiliza la aplicación. Por ejemplo, si su DbContext tiene un DbSet<Album> , tiene que migrar la Album
clase.
Con estos archivos en su lugar, el Startup.cs archivo puede hacerse para compilar mediante la actualización de su
using instrucciones:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
Nuestra aplicación ahora está preparado para admitir la autenticación y los servicios de identidad. Basta con que
tienen estas características expuestas a los usuarios.
Ahora, agregue una nueva vista Razor denominada _LoginPartial a la vistas/compartidas carpeta:
Actualización _LoginPartial.cshtml con el código siguiente (reemplazar todo su contenido):
@if (SignInManager.IsSignedIn(User))
{
<form asp-area="" asp-controller="Account" asp-action="Logout" method="post" id="logoutForm"
class="navbar-right">
<ul class="nav navbar-nav navbar-right">
<li>
<a asp-area="" asp-controller="Manage" asp-action="Index" title="Manage">Hello
@UserManager.GetUserName(User)!</a>
</li>
<li>
<button type="submit" class="btn btn-link navbar-btn navbar-link">Log out</button>
</li>
</ul>
</form>
}
else
{
<ul class="nav navbar-nav navbar-right">
<li><a asp-area="" asp-controller="Account" asp-action="Register">Register</a></li>
<li><a asp-area="" asp-controller="Account" asp-action="Login">Log in</a></li>
</ul>
}
Resumen
ASP.NET Core incluye cambios en las características de ASP.NET Identity. En este artículo, ha visto cómo migrar
las características de administración de autenticación y usuario de identidad de ASP.NET a ASP.NET Core.
Migrar desde ClaimsPrincipal.Current
22/06/2018 • 5 minutes to read • Edit Online
En los proyectos ASP.NET, era habitual usar ClaimsPrincipal.Current recuperar la actual autenticado notificaciones
y la identidad del usuario. En ASP.NET Core, ya no se establece esta propiedad. El código que se según lo debe
actualizarse para obtener la identidad del usuario autenticado actual a través de un medio diferente.
El código de ejemplo anterior se establece Thread.CurrentPrincipal y comprueba su valor antes y después esperar
una llamada asincrónica. Thread.CurrentPrincipal es específico de la subproceso en el que se establece y el
método es apropiado reanudar la ejecución en un subproceso diferente después de la instrucción await. Por lo
tanto, Thread.CurrentPrincipal está presente cuando se comprueba en primer lugar, pero es null después de
llamar a await Task.Yield() .
Obtener la identidad del usuario actual de recopilación de la aplicación de servicio DI es más comprobable,
demasiado, ya que las identidades de prueba se pueden insertar fácilmente.
La última opción, mediante IHttpContextAccessor , es contraria a principios de ASP.NET Core (por lo que prefieren
dependencias insertadas dependencias estáticas). Previsto eliminar eventualmente la dependencia en el método
estático IHttpContextAccessor auxiliar. Puede ser un puente útil, sin embargo, al migrar grandes aplicaciones
ASP.NET existentes que anteriormente estaban usando ClaimsPrincipal.Current .
Migrar de autenticación de pertenencia a ASP.NET a
ASP.NET Core 2.0 Identity
22/06/2018 • 11 minutes to read • Edit Online
NOTE
Este documento proporciona los pasos necesarios para migrar el esquema de base de datos para aplicaciones basadas en la
pertenencia a ASP.NET para el esquema de base de datos usado para identidad de núcleo de ASP.NET. Para obtener más
información sobre cómo migrar de autenticación basada en pertenencia a ASP.NET a ASP.NET Identity, consulte migrar una
aplicación existente de SQL pertenencia a ASP.NET Identity. Para obtener más información acerca de la identidad de núcleo de
ASP.NET, vea Introducción a la identidad en ASP.NET Core.
Para migrar aplicaciones existentes a ASP.NET Core 2.0 Identity, los datos en estas tablas tienen que migrarse a las
tablas utilizadas por el nuevo esquema de identidad.
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=aspnet-core-
identity;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
Este comando crea la base de datos especificada con el esquema y los datos necesarios para la inicialización de la
aplicación. La siguiente imagen muestra la estructura de tabla que se crea con los pasos anteriores.
migrar el esquema
Existen diferencias sutiles en los campos para la suscripción y ASP.NET Core Identity y estructuras de tabla. El
modelo ha cambiado considerablemente para la autenticación/autorización con aplicaciones ASP.NET y ASP.NET
Core. Los objetos de clave que todavía se utilizan con identidad están usuarios y Roles. Estas son las tablas de
asignación de usuarios, Roles, y UserRoles.
Usuarios
MEMBERSHIP(ASPNET_USERS/A
IDENTITY(ASPNETUSERS) SPNET_MEMBERSHIP)
NOTE
No todas las asignaciones de campo son similares a las relaciones uno a uno de pertenencia a ASP.NET Core Identity. La tabla
anterior toma el esquema de usuario de pertenencia predeterminado y lo asigna al esquema de núcleo de ASP.NET Identity.
Los campos personalizados que se utilizaron para la suscripción es necesario asignar manualmente. En esta asignación, no
hay ninguna asignación para las contraseñas, como criterios de contraseña y Sales de la contraseña no se migran entre los
dos. Se recomienda dejar la contraseña como null y pedir a los usuarios restablecer sus contraseñas. En ASP.NET
Core Identity, LockoutEnd debe establecerse en una fecha en el futuro si el usuario está bloqueado. Esto se muestra en el
script de migración.
Roles
IDENTITY(ASPNETROLES) MEMBERSHIP(ASPNET_ROLES)
Roles de usuario
MEMBERSHIP(ASPNET_USERSI
IDENTITY(ASPNETUSERROLES) NROLES)
-- INSERT USERS
INSERT INTO coreidentity.dbo.aspnetusers
(id,
username,
normalizedusername,
passwordhash,
securitystamp,
emailconfirmed,
phonenumber,
phonenumberconfirmed,
twofactorenabled,
lockoutend,
lockoutenabled,
accessfailedcount,
email,
normalizedemail)
SELECT aspnet_users.userid,
aspnet_users.username,
aspnet_users.loweredusername,
--Creates an empty password since passwords don't map between the two schemas
'',
--Security Stamp is a token used to verify the state of an account and is subject to change at any
time. It should be intialized as a new ID.
NewID(),
--EmailConfirmed is set when a new user is created and confirmed via email. Users must have this set
during migration to ensure they're able to reset passwords.
1,
aspnet_users.mobilealias,
CASE
WHEN aspnet_Users.MobileAlias is null THEN 0
ELSE 1
END,
--2-factor Auth likely wasn't setup in Membership for users, so setting as false.
0,
CASE
--Setting lockout date to time in the future (1000 years)
WHEN aspnet_membership.islockedout = 1 THEN Dateadd(year, 1000,
Sysutcdatetime())
ELSE NULL
END,
aspnet_membership.islockedout,
--AccessFailedAccount is used to track failed logins. This is stored in membership in multiple columns.
Setting to 0 arbitrarily.
0,
aspnet_membership.email,
aspnet_membership.loweredemail
FROM aspnet_users
LEFT OUTER JOIN aspnet_membership
ON aspnet_membership.applicationid =
aspnet_users.applicationid
AND aspnet_users.userid = aspnet_membership.userid
LEFT OUTER JOIN coreidentity.dbo.aspnetusers
ON aspnet_membership.userid = aspnetusers.id
WHERE aspnetusers.id IS NULL
-- INSERT ROLES
INSERT INTO coreIdentity.dbo.aspnetroles(id,name)
SELECT roleId,rolename
SELECT roleId,rolename
FROM aspnet_roles;
IF @@ERROR <> 0
BEGIN
ROLLBACK TRANSACTION MigrateUsersAndRoles
RETURN
END
Tras finalizar este script, la aplicación de ASP.NET Core Identity que se creó anteriormente se rellena con los
usuarios de pertenencia. Los usuarios deben cambiar sus contraseñas antes de iniciar sesión.
NOTE
Si el sistema de pertenencia tenía usuarios con nombres de usuario que no coincidía con su dirección de correo electrónico,
se requiere para la aplicación que creó anteriormente para dar cabida a este cambio. La plantilla predeterminada espera
UserName y Email para que coincidan. En situaciones en las que son diferentes, es necesario modificar para usar el
proceso de inicio de sesión UserName en lugar de Email .
Pasos siguientes
En este tutorial, aprendió cómo trasladar a los usuarios de pertenencia SQL ASP.NET Core 2.0 Identity. Para
obtener más información sobre ASP.NET Core Identity, consulte Introducción a la identidad.
Migrar controladores HTTP y módulos ASP.NET
Core middleware
22/06/2018 • 26 minutes to read • Edit Online
Tenga en cuenta cómo en la imagen anterior, el middleware de autenticación había cortocircuitado la solicitud.
// ASP.NET 4 module
using System;
using System.Web;
namespace MyApp.Modules
{
public class MyModule : IHttpModule
{
public void Dispose()
{
}
Como se muestra en el Middleware página, un middleware de ASP.NET Core es una clase que expone un
Invoke tomar método un HttpContext y devolver un Task . El middleware de nuevo tendrá un aspecto similar al
siguiente:
// ASP.NET Core middleware
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
namespace MyApp.Middleware
{
public class MyMiddleware
{
private readonly RequestDelegate _next;
await _next.Invoke(context);
// Clean up.
}
}
if (TerminateRequest())
{
context.Response.End();
return;
}
}
if (!TerminateRequest())
await _next.Invoke(context);
// Clean up.
}
Cuando se migra la funcionalidad de su módulo el middleware de nuevo, es posible que el código no se compila
porque la HttpContext clase ha cambiado significativamente en ASP.NET Core. Más adelante en, verá cómo
migrar a la nueva HttpContext principales de ASP.NET.
Convertir esto por agregar su middleware nueva a la canalización de solicitud en su Startup clase:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseMyMiddleware();
app.UseMyMiddlewareWithParams();
app.UseMyTerminatingMiddleware();
app.MapWhen(
context => context.Request.Path.ToString().EndsWith(".context"),
appBranch => {
appBranch.UseHttpContextDemoMiddleware();
});
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
El lugar exacto de la canalización donde insertar el middleware nueva depende de los eventos que tratan como
un módulo ( BeginRequest , EndRequest , etc.) y su orden en la lista de módulos de Web.config.
Como anteriormente indicó, no hay ningún ciclo de vida de la aplicación de ASP.NET Core y el orden en que se
procesan las respuestas middleware distinto del orden usado por módulos. Esto podría tomar una decisión de
ordenación más complejo.
Si la ordenación se convierte en un problema, puede dividir el módulo de varios componentes de software
intermedio que se pueden ordenar de forma independiente.
// ASP.NET 4 handler
using System.Web;
namespace MyApp.HttpHandlers
{
public class MyHandler : IHttpHandler
{
public bool IsReusable { get { return true; } }
context.Response.ContentType = GetContentType();
context.Response.Output.Write(response);
}
// ...
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
namespace MyApp.Middleware
{
public class MyHandlerMiddleware
{
// Must have constructor with this signature, otherwise exception at run time
public MyHandlerMiddleware(RequestDelegate next)
{
// This is an HTTP Handler, so no need to store next
}
context.Response.ContentType = GetContentType();
await context.Response.WriteAsync(response);
}
// ...
Este middleware es muy similar del middleware correspondiente a los módulos. La única diferencia es que aquí
no hay ninguna llamada a _next.Invoke(context) . Que tiene sentido, porque el controlador no es el final de la
canalización de solicitud, por lo que no habrá ningún siguiente middleware para invocar.
Puede convertir este agregando el middleware de controlador nuevo a la canalización de solicitud en su Startup
(clase), similar a middleware convertido a partir de los módulos. El problema con este enfoque es que enviaría
todas las solicitudes para el middleware de controlador nuevo. Sin embargo, sólo desea que las solicitudes con
una extensión específica para alcanzar el middleware. Esto le proporcionaría la misma funcionalidad que no
tenga el controlador HTTP.
Una solución es crear una bifurcación de la canalización de solicitudes con una extensión determinada, mediante
el MapWhen método de extensión. Para ello, en el mismo Configure método donde se agrega el middleware de
otro:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseMyMiddleware();
app.UseMyMiddlewareWithParams();
app.UseMyTerminatingMiddleware();
app.MapWhen(
context => context.Request.Path.ToString().EndsWith(".context"),
appBranch => {
appBranch.UseHttpContextDemoMiddleware();
});
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
{
"MyMiddlewareOptionsSection": {
"Param1": "Param1Value",
"Param2": "Param2Value"
}
}
MyMiddlewareOptionsSection aquí es un nombre de sección. No tiene que ser el mismo que el nombre de
la clase de opciones.
3. Asociar a los valores de opción de la clase de opciones
El patrón de opciones usa framework de inyección de dependencia de ASP.NET Core para asociar el tipo
de opciones (como MyMiddlewareOptions ) con un MyMiddlewareOptions objeto que tiene las opciones
reales.
Actualización de su Startup clase:
a. Si usas appSettings.JSON que se, agregar para el generador de configuración de la Startup
constructor:
await _next.Invoke(context);
{
"MyMiddlewareOptionsSection2": {
"Param1": "Param1Value2",
"Param2": "Param2Value2"
},
"MyMiddlewareOptionsSection": {
"Param1": "Param1Value",
"Param2": "Param2Value"
}
}
2. Recuperar valores de las opciones y pasarlos a middleware. El Use... método de extensión (que agrega el
middleware a la canalización) es un punto lógico para pasar los valores de opción:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseMyMiddleware();
app.UseMyMiddlewareWithParams();
var myMiddlewareOptions =
Configuration.GetSection("MyMiddlewareOptionsSection").Get<MyMiddlewareOptions>();
var myMiddlewareOptions2 =
Configuration.GetSection("MyMiddlewareOptionsSection2").Get<MyMiddlewareOptions>();
app.UseMyMiddlewareWithParams(myMiddlewareOptions);
app.UseMyMiddlewareWithParams(myMiddlewareOptions2);
app.UseMyTerminatingMiddleware();
app.MapWhen(
context => context.Request.Path.ToString().EndsWith(".context"),
appBranch => {
appBranch.UseHttpContextDemoMiddleware();
});
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
Tenga en cuenta cómo se ajusta el objeto de opciones en un OptionsWrapper objeto. Esto implementa
IOptions , tal y como se esperaba el constructor de middleware.
HttpContext ha cambiado significativamente en ASP.NET Core. Esta sección muestra cómo traducir las
propiedades más utilizadas de System.Web.HttpContext al nuevo Microsoft.AspNetCore.Http.HttpContext .
HttpContext
HttpContext.Items se convierte en:
HttpContext.Request
HttpContext.Request.HttpMethod se convierte en:
// using Microsoft.AspNetCore.Http.Extensions;
var url = httpContext.Request.GetDisplayUrl();
// using Microsoft.AspNetCore.Http.Headers;
// using Microsoft.Net.Http.Headers;
// For unknown header, unknownheaderValues has zero items and unknownheaderValue is ""
IList<string> unknownheaderValues = headersDictionary["unknownheader"];
string unknownheaderValue = headersDictionary["unknownheader"].ToString();
// For known header, knownheaderValues has 1 item and knownheaderValue is the value
IList<string> knownheaderValues = headersDictionary[HeaderNames.AcceptLanguage];
string knownheaderValue = headersDictionary[HeaderNames.AcceptLanguage].ToString();
// using Microsoft.Net.Http.Headers;
if (httpContext.Request.HasFormContentType)
{
IFormCollection form;
WARNING
Leer valores de formulario sólo si el tipo de contenido sub es x--www-form-urlencoded o datos del formulario.
string inputBody;
using (var reader = new System.IO.StreamReader(
httpContext.Request.Body, System.Text.Encoding.UTF8))
{
inputBody = reader.ReadToEnd();
}
WARNING
Use este código solo en un middleware de tipo de controlador, al final de una canalización.
Puede leer el cuerpo sin formato, como se indicó anteriormente una sola vez por solicitud. Middleware de intentar leer el
cuerpo después de la primera lectura leerá un cuerpo vacío.
Esto no se aplica a un formulario de lectura como se muestra anteriormente, ya proviene de un búfer.
HttpContext.Response
HttpContext.Response.Status y HttpContext.Response.StatusDescription traducir al:
// using Microsoft.AspNetCore.Http;
httpContext.Response.StatusCode = StatusCodes.Status200OK;
// using Microsoft.Net.Http.Headers;
var mediaType = new MediaTypeHeaderValue("application/json");
mediaType.Encoding = System.Text.Encoding.UTF8;
httpContext.Response.ContentType = mediaType.ToString();
httpContext.Response.ContentType = "text/html";
HttpContext.Response.TransmitFile
Servir el archivo se analiza aquí.
HttpContext.Response.Headers
Enviar encabezados de respuesta se ve complicado por el hecho de que si se establece después de que algo se ha
escrito en el cuerpo de respuesta, no se enviará.
La solución consiste en establecer un método de devolución de llamada que se llamará derecha antes de escribir
en la respuesta se inicia. Es mejor hacerlo al principio de la Invoke método en el middleware. Es este método de
devolución de llamada que establece los encabezados de respuesta.
El código siguiente define un método de devolución de llamada llama SetHeaders :
HttpContext.Response.Cookies
Las cookies de viaje en el explorador en un Set-Cookie encabezado de respuesta. Como resultado, al enviar
cookies, requiere la misma devolución de llamada que se usan para enviar los encabezados de respuesta:
responseCookies.Append("cookie1name", "cookie1value");
responseCookies.Append("cookie2name", "cookie2value",
new CookieOptions { Expires = System.DateTime.Now.AddDays(5), HttpOnly = true });
Recursos adicionales
Información general de los módulos HTTP y controladores HTTP
Configuración
Inicio de aplicaciones
Middleware
Migración de ASP.NET Core 1.x a 2.0
21/06/2018 • 13 minutes to read • Edit Online
Requisitos previos
Consulte Introducción a ASP.NET Core.
<TargetFramework>netcoreapp2.0</TargetFramework>
Los proyectos para .NET Framework deben usar el TFM de una versión mayor o igual que .NET Framework 4.6.1.
Busque el nodo <TargetFramework> del archivo .csproj y reemplace su texto interno por net461 :
<TargetFramework>net461</TargetFramework>
NOTE
.NET Core 2.0 ofrece un área expuesta mucho mayor que .NET Core 1.x. Si el destino es .NET Framework solo porque faltan
API en .NET Core 1.x, el uso de .NET Core 2.0 como destino es probable que dé resultado.
{
"sdk": {
"version": "2.0.0"
}
}
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
</ItemGroup>
Todas las características de ASP.NET Core 2.0 y Entity Framework Core 2.0 están incluidas en el metapaquete.
Los proyectos de ASP.NET Core 2.0 para .NET Framework deben seguir haciendo referencia a paquetes NuGet
individuales. Actualización del atributo Version de cada nodo <PackageReference /> a 2.0.0.
Por ejemplo, esta es la lista de nodos <PackageReference /> usados en un proyecto típico de ASP.NET Core 2.0
para .NET Framework:
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.ViewCompilation" Version="2.0.0"
PrivateAssets="All" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="2.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.0"
PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
<DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="2.0.0" />
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
</ItemGroup>
<PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81;</PackageTargetFallback>
using System.IO;
using Microsoft.AspNetCore.Hosting;
namespace AspNetCoreDotNetCore1App
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.UseApplicationInsights()
.Build();
host.Run();
}
}
}
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
namespace AspNetCoreDotNetCore2App
{
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
La adopción de este nuevo patrón 2.0 es muy recomendable y necesaria para que funcionen características de
producto como las migraciones de Entity Framework (EF ) Core. Por ejemplo, la ejecución de Update-Database
desde la ventana Consola del Administrador de paquetes o de dotnet ef database update desde la línea de
comandos (en proyectos convertidos a ASP.NET Core 2.0) genera el error siguiente:
if (env.IsDevelopment())
{
builder.AddUserSecrets<Startup>();
}
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
El ejemplo anterior carga el miembro Configuration con opciones de configuración de appsettings.json, así como
cualquier archivo appsettings.<EnvironmentName>.json que coincida con la propiedad
IHostingEnvironment.EnvironmentName . La ubicación de estos archivos está en la misma ruta de acceso que
Startup.cs.
En los proyectos de 2.0, el código de configuración reutilizable inherente a los proyectos de 1.x se ejecuta en
segundo plano. Por ejemplo, las variables de entorno y la configuración de la aplicación se cargan durante el inicio.
El código de Startup.cs equivalente se reduce a la inicialización de IConfiguration con la instancia insertada:
La configuración que el método CreateDefaultBuilder utiliza en el fragmento de código anterior puede verse aquí.
Para más información, consulte Configuración en ASP.NET Core.
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
SeedData.Initialize(app.ApplicationServices);
try
{
// Requires using RazorPagesMovie.Models;
SeedData.Initialize(services);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred seeding the DB.");
}
}
host.Run();
A partir de 2.0 se desaconseja cualquier acción en BuildWebHost , excepto la compilación y la configuración del
host web. Todo lo relacionado con la ejecución de la aplicación deberá gestionarse fuera de BuildWebHost —,
normalmente en el método Main de Program.cs.
host.Run();
}
3. Quite la llamada API del lado cliente de Application Insights de _Layout.cshtml. Comprende las dos líneas
de código siguientes:
Si usa el SDK de Application Insights directamente, siga haciéndolo. El metapaquete 2.0 incluye la versión más
reciente de Application Insights, por lo que si se hace referencia a una versión anterior, aparece un error de
degradación de paquete.
Recursos adicionales
Cambios importantes en ASP.NET Core 2.0
Migrar de autenticación e identidad a ASP.NET Core
2.0
22/06/2018 • 14 minutes to read • Edit Online
En los 2.0 proyectos, se configura la autenticación a través de servicios. Cada esquema de autenticación está
registrado en el ConfigureServices método Startup.cs. El UseIdentity método se sustituye por
UseAuthentication .
app.UseAuthentication();
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
app.UseAuthentication();
app.UseAuthentication();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Audience = "http://localhost:5001/";
options.Authority = "http://localhost:5000/";
});
Este fragmento de código no utiliza la identidad, por lo que el esquema predeterminado debe establecerse
pasando JwtBearerDefaults.AuthenticationScheme a la AddAuthentication método.
Autenticación de OpenID conectarse (OIDC )
Realice los cambios siguientes en Startup.cs:
Reemplace el UseOpenIdConnectAuthentication llamada al método el Configure método con
UseAuthentication :
app.UseAuthentication();
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.Authority = Configuration["auth:oidc:authority"];
options.ClientId = Configuration["auth:oidc:clientid"];
});
autenticación de Facebook
Realice los cambios siguientes en Startup.cs:
Reemplace el UseFacebookAuthentication llamada al método el Configure método con UseAuthentication :
app.UseAuthentication();
services.AddAuthentication()
.AddFacebook(options =>
{
options.AppId = Configuration["auth:facebook:appid"];
options.AppSecret = Configuration["auth:facebook:appsecret"];
});
Autenticación de Google
Realice los cambios siguientes en Startup.cs:
Reemplace el UseGoogleAuthentication llamada al método el Configure método con UseAuthentication :
app.UseAuthentication();
services.AddAuthentication()
.AddGoogle(options =>
{
options.ClientId = Configuration["auth:google:clientid"];
options.ClientSecret = Configuration["auth:google:clientsecret"];
});
app.UseAuthentication();
services.AddAuthentication()
.AddMicrosoftAccount(options =>
{
options.ClientId = Configuration["auth:microsoft:clientid"];
options.ClientSecret = Configuration["auth:microsoft:clientsecret"];
});
Autenticación de Twitter
Realice los cambios siguientes en Startup.cs:
Reemplace el UseTwitterAuthentication llamada al método el Configure método con UseAuthentication :
app.UseAuthentication();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme);
Como alternativa, use una versión sobrecargada de la AddAuthentication método para establecer más de una
propiedad. En el siguiente ejemplo de método sobrecargado, el esquema predeterminado se establece en
CookieAuthenticationDefaults.AuthenticationScheme . O bien se puede especificar el esquema de autenticación
dentro de la persona [Authorize] atributos o las directivas de autorización.
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
});
services.AddAuthentication(IISDefaults.AuthenticationScheme);
Instancias de IdentityCookieOptions
Un efecto secundario de los 2.0 cambios es el conmutador que se va a usar con el nombre de opciones en lugar de
instancias de opciones de cookie. Se quita la capacidad de personalizar los nombres de esquema de cookie de
identidad.
Por ejemplo, los proyectos utilizan 1.x inyección de constructor para pasar un IdentityCookieOptions parámetros
en AccountController.cs. El esquema de autenticación de cookie externo se tiene acceso desde la instancia
proporcionada:
public AccountController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
IOptions<IdentityCookieOptions> identityCookieOptions,
IEmailSender emailSender,
ISmsSender smsSender,
ILoggerFactory loggerFactory)
{
_userManager = userManager;
_signInManager = signInManager;
_externalCookieScheme = identityCookieOptions.Value.ExternalCookieAuthenticationScheme;
_emailSender = emailSender;
_smsSender = smsSender;
_logger = loggerFactory.CreateLogger<AccountController>();
}
/// <summary>
/// Navigation property for the roles this user belongs to.
/// </summary>
public virtual ICollection<IdentityUserRole<int>> Roles { get; } = new List<IdentityUserRole<int>>();
/// <summary>
/// Navigation property for the claims this user possesses.
/// </summary>
public virtual ICollection<IdentityUserClaim<int>> Claims { get; } = new List<IdentityUserClaim<int>>();
/// <summary>
/// Navigation property for this users login accounts.
/// </summary>
public virtual ICollection<IdentityUserLogin<int>> Logins { get; } = new List<IdentityUserLogin<int>>();
Para evitar que las claves externas duplicadas al ejecutar migraciones de núcleo de EF, agregue lo siguiente a su
IdentityDbContext clase OnModelCreating método (después de la base.OnModelCreating(); llamar a):
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
builder.Entity<ApplicationUser>()
.HasMany(e => e.Claims)
.WithOne()
.HasForeignKey(e => e.UserId)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
builder.Entity<ApplicationUser>()
.HasMany(e => e.Logins)
.WithOne()
.HasForeignKey(e => e.UserId)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
builder.Entity<ApplicationUser>()
.HasMany(e => e.Roles)
.WithOne()
.HasForeignKey(e => e.UserId)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
}
Reemplazar GetExternalAuthenticationSchemes
El método sincrónico GetExternalAuthenticationSchemes se quitó en favor de una versión asincrónica. 1.x
proyectos tienen el siguiente código ManageController.cs:
using System.Collections.Generic;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Identity;
namespace AspNetCoreDotNetCore1App.Models.ManageViewModels
{
public class ManageLoginsViewModel
{
public IList<UserLoginInfo> CurrentLogins { get; set; }
En los 2.0 proyectos, se cambia el tipo de valor devuelto a IList<AuthenticationScheme> . Este nuevo tipo de valor
devuelto es necesario sustituir la Microsoft.AspNetCore.Http.Authentication importar con un
Microsoft.AspNetCore.Authentication importar.
using System.Collections.Generic;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
namespace AspNetCoreDotNetCore2App.Models.ManageViewModels
{
public class ManageLoginsViewModel
{
public IList<UserLoginInfo> CurrentLogins { get; set; }
Recursos adicionales
Para obtener más detalles y explicación, consulte el discusión para autenticación 2.0 problema en GitHub.