Простейший CRUD интерфейс

Последнее обновление: 18.02.2020

Создадим приложение на ASP.NET Core в связке с Angular, которое будет реализовать простейший CRUD интерфейс, то есть выполнять базовые опреации по чтению, добавлению, изменению и удалению данных. За основу возьмем проект из прошлой темы:

Первый проект Angular в ASP.NET Core

Сервис Web API

Вначале определим логику на стороне сервера. Для этого создадим в проекте новую папку Models, которая будет содержать модели. Затем добавим в нее класс Product:

public class Product
{
	public int Id { get; set; }
	public string Name { get; set; }	// название 
	public string Company { get; set; }	// производитель
	public int Price { get; set; }	// цена
}

Данный класс представляет данные о товарах, которые будут храниться в базе данных.

Для работы с БД добавим в проект через Nuget пакет Microsoft.EntityFrameworkCore.SqlServer. И затем в папке Models определим класс контекста данных ApplicationContext:

using Microsoft.EntityFrameworkCore;

namespace HelloAngularApp.Models
{
    public class ApplicationContext : DbContext
    {
        public ApplicationContext(DbContextOptions<ApplicationContext> options) 
            : base(options)
        { 
			Database.EnsureCreated();
		}

        public DbSet<Product> Products { get; set; }
    }
}

Далее определим собственно ту логику, которая будет выполняться на сервере. Добавим в проект папку Controllers. Затем в этой папке определим класс контроллера ProductController:

using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using HelloAngularApp.Models;

namespace HelloAngularApp.Controllers
{
    [ApiController]
    [Route("api/products")]
    public class ProductController : Controller
    {
        ApplicationContext db;
        public ProductController(ApplicationContext context)
        {
            db = context;
            if (!db.Products.Any())
            {
                db.Products.Add(new Product { Name = "iPhone X", Company = "Apple", Price = 79900 });
                db.Products.Add(new Product { Name = "Galaxy S8", Company = "Samsung", Price = 49900 });
                db.Products.Add(new Product { Name = "Pixel 2", Company = "Google", Price = 52900 });
                db.SaveChanges();
            }
        }
        [HttpGet]
        public IEnumerable<Product> Get()
        {
            return db.Products.ToList();
        }

        [HttpGet("{id}")]
        public Product Get(int id)
        {
            Product product = db.Products.FirstOrDefault(x => x.Id == id);
            return product;
        }

        [HttpPost]
        public IActionResult Post(Product product)
        {
            if (ModelState.IsValid)
            {
                db.Products.Add(product);
                db.SaveChanges();
                return Ok(product);
            }
            return BadRequest(ModelState);
        }

        [HttpPut]
        public IActionResult Put(Product product)
        {
            if (ModelState.IsValid)
            {
                db.Update(product);
                db.SaveChanges();
                return Ok(product);
            }
            return BadRequest(ModelState);
        }

        [HttpDelete("{id}")]
        public IActionResult Delete(int id)
        {
            Product product = db.Products.FirstOrDefault(x => x.Id == id);
            if (product != null)
            {
                db.Products.Remove(product);
                db.SaveChanges();
            }
            return Ok(product);
        }
    }
}

Контроллер получает через конструктор констект данных и через него производит различные операции с данными из БД.

И изменим файл Startup.cs:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.SpaServices.AngularCli;
using Microsoft.EntityFrameworkCore;
using HelloAngularApp.Models;

namespace HelloAngularApp
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            string connectionString = "Server=(localdb)\\mssqllocaldb;Database=productsdb;Trusted_Connection=True;";
            services.AddDbContext<ApplicationContext>(options => options.UseSqlServer(connectionString));

            services.AddControllers();

            services.AddSpaStaticFiles(configuration =>
            {
                configuration.RootPath = "ClientApp/dist";
            });
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseStaticFiles();
            if (!env.IsDevelopment())
            {
                app.UseSpaStaticFiles();
            }

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });

            app.UseSpa(spa =>
            {
                spa.Options.SourcePath = "ClientApp";
                
                if (env.IsDevelopment())
                {
                    spa.UseAngularCliServer(npmScript: "start");
                }
            });
        }
    }
}

Для работ с контроллером здесь добавляются соответствующие сервисы (services.AddControllers()). И также регистрируется контекст данных как сервис.

В методе Configure настраивается маршрутизация для контроллеров Web API (endpoints.MapControllers()).

Модель данных

После создания серверной части изменим клиентскую часть. В проекте в папке ClientApp/src/app создадим файл product.ts:

export class Product {
    constructor(
        public id?: number,
        public name?: string,
        public company?: string,
        public price?: number) { }
}

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

Создание http-сервиса

Также в папку ClientApp/src/app добавим новый файл data.service.ts:

import { Injectable } from '@angular/core';
import { HttpClient} from '@angular/common/http';
import { Product } from './product';

@Injectable()
export class DataService {

    private url = "/api/products";

    constructor(private http: HttpClient) {
    }

    getProducts() {
        return this.http.get(this.url);
    }
	
	getProduct(id: number) {
        return this.http.get(this.url + '/' + id);
    }
	
    createProduct(product: Product) {
        return this.http.post(this.url, product);
    }
    updateProduct(product: Product) {
 
        return this.http.put(this.url, product);
    }
    deleteProduct(id: number) {
        return this.http.delete(this.url + '/' + id);
    }
}

Здесь определен сервис, через который клиент будет взаимодействовать с сервером. Чтобы его можно было внедрять в другие классы, к этому сервису применяется декоратор @Injectable.

Сервис определяет четыре метода для отправки данных на сервер различным методам контроллера ProductController. Поскольку этот контроллер будет запускаться по адресу "/api/products", то соответственно по этому адресу будут отправляться запросы. Для отправки запросов используется класс HttpClient из пакета @angular/common/http.

Чтобы получить все данные с сервера, выполняется метод getProducts:

getProducts() {
	return this.http.get(this.url);
}

Здесь вызывается метод get объекта HttpClient, в который передается адрес сервера.

В методе createProduct() отправляем новый объект Product на сервер.

createProduct(product: Product) {
	return this.http.post(this.url, product);
}

Метод updateProduct() получает через параметр измененный объект и отправляет его на сервер с помощью метода put класса HttpClient:

updateProduct(product: Product) {
	return this.http.put(this.url, product);
}

В метод put также передается адрес и отправляемый объект.

И в методе deleteProduct() производится удаление объекта по id:

deleteProduct(id: number) {
	return this.http.delete(this.url + '/' + id);
}

Создание компонента

Используем этот сервис. Для этого определим в папке ClientApp/src/app следующий файл app.component.ts:

import { Component, OnInit } from '@angular/core';
import { DataService } from './data.service';
import { Product } from './product';

@Component({
    selector: 'app',
    templateUrl: './app.component.html',
    providers: [DataService]
})
export class AppComponent implements OnInit {

    product: Product = new Product();   // изменяемый товар
    products: Product[];                // массив товаров
    tableMode: boolean = true;          // табличный режим

    constructor(private dataService: DataService) { }

    ngOnInit() {
        this.loadProducts(); 	// загрузка данных при старте компонента  
    }
	// получаем данные через сервис
    loadProducts() {
        this.dataService.getProducts()
            .subscribe((data: Product[]) => this.products = data);
    }
	// сохранение данных
    save() {
        if (this.product.id == null) {
            this.dataService.createProduct(this.product)
                .subscribe((data: Product) => this.products.push(data));
        } else {
            this.dataService.updateProduct(this.product)
                .subscribe(data => this.loadProducts());
        }
        this.cancel();
    }
    editProduct(p: Product) {
        this.product = p;
    }
    cancel() {
        this.product = new Product();
        this.tableMode = true;
    }
    delete(p: Product) {
        this.dataService.deleteProduct(p.id)
            .subscribe(data => this.loadProducts());
    }
    add() {
        this.cancel();
        this.tableMode = false;
    }
}

Для использования сервис DataService добавляется в свойстве providers. И затем мы сможем получить этот сервис через конструктор класса AppComponent.

Класс компонента определяет три переменных. Переменная product представляет товар, который будет добавляться или редактироваться. Переменная tableMode представляет логическое значение, которое указывает, находимся ли мы в процессе добавления нового объекта или в процессе просмотра всех объектов в виде таблицы. А переменнаяproducts хранит все полученные с сервера данные.

В методе OnInit(), который выполняется при запуске компонента, будет выполняться начальная загрузка данных. Сама загрузка данных производится в методе loadProducts(). Здесь идет обращение к методу getProducts, который определен в сервисе. Запрос фактически выполняется после вызова функции subscribe(), в которой через параметр стрелочной функции получаем данные и передаем эти данные переменной products.

Метод save() сохраняет объект. Если id объекта не установлен, то мы имеем дело с добавлением нового объекта. В зависимости от того, что происходит - добавление или изменение вызывается определенный метод сервиса. Полученный с сервера при добавлении объект затем добавляется в массив products. При редактировании просто перезагружаем данные.

Метод editProduct() устанавливает редактируемый объект.

Метод cancel() сбрасывает состояние к начальному.

Метод delete() удаляет объект, вызывая соответствующий метод сервиса.

Метод add() изменяем режим и фактически переключает пользователя с просмотра объектов в виде таблицы на добавление нового объекта.

Создание представления

Класс AppComponent для определения визуальной части использует файл app.component.html. Определим этот файл в папке ClientApp/src/app и определим в нем следующее содержимое:

<h1>Список моделей</h1>
<input type="button" value="Добавить" class="btn btn-default" (click)="add()" />
<table *ngIf="tableMode; else create" class="table table-striped">
    <thead>
        <tr>
            <td>Модель</td>
            <td>Производитель</td>
            <td>Цена</td>
            <td></td>
        </tr>
    </thead>
    <tbody>
        <tr *ngFor="let p of products">
            <ng-template [ngIf]="product?.id != p.id" [ngIfElse]="edit">
                <td>{{p?.name}}</td>
                <td>{{p?.company}}</td>
                <td>{{p?.price}}</td>
                <td>
                    <button class="btn btn-sm btn-primary" (click)="editProduct(p)">Изменить</button>
                    <button class="btn btn-sm btn-danger" (click)="delete(p)">Удалить</button>
                </td>
            </ng-template>
        </tr>
    </tbody>
</table>

<!--шаблон для редактирования-->
<ng-template #edit>
    <td>
        <input type="text" [(ngModel)]="product.name" class="form-control" />
    </td>
    <td>
        <input type="text" [(ngModel)]="product.company" class="form-control" />
    </td>
    <td>
        <input type="number" [(ngModel)]="product.price" class="form-control" />
    </td>
    <td>
        <input type="button" value="Сохранить" (click)="save()" class="btn btn-sm btn-success" />
        <input type="button" value="Отмена" (click)="cancel()" class="btn btn-sm btn-warning" />
    </td>
</ng-template>

<!--шаблон для добавления-->
<ng-template #create>
    <div class="form-group">
        <label>Модель</label>
        <input type="text" [(ngModel)]="product.name" class="form-control" />
    </div>
    <div class="form-group">
        <label>Производитель</label>
        <input type="text" [(ngModel)]="product.company" class="form-control" />
    </div>
    <div class="form-group">
        <label>Цена</label>
        <input type="number" [(ngModel)]="product.price" class="form-control" />
    </div>
    <div>
        <input type="button" value="Сохранить" (click)="save()" class="btn btn-success" />
        <input type="button" value="Отмена" (click)="cancel()" class="btn btn-warning" />
    </div>
</ng-template>

В начале разметки определена кнопка, которая переключает режим страницы с просмотра данных на их добавление. Затем собственно идет таблица с данными. Причем таблица использует директиву *ngIf="tableMode; else create", то есть если tableMode равна true, то отображается данный элемент table. Иначе вместо элемента table отображается разметка шаблона create, который определен в конце файла. Шаблон create содержит набор полей для ввода данных объекта Product. То есть с помощью кнопки мы можем переключаться с элемента table на шаблон create и обратно.

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

<ng-template [ngIf]="product?.id != p.id" [ngIfElse]="edit">

То есть если id объекта из переменной product НЕ равен id текущему объекту, то просто выводим данные. Если id обоих объектов равны, то вместо этого шаблона применяется шаблон edit, который определен в конце файла.

В какой ситуации id объектов будут равны? Для каждой строки определены две кнопки - для удаления и изменения. При нажатии на кнопку изменения переменной product передается тот объект, который мы хотим изменить. А это значит что id у данного объекта из таблицы равно id объекта из переменной product, поэтому для данного объекта применяется шаблон edit, который содержит поля для редактирования.

Изменение модуля

Чтобы взаимодействие с сервером заработало, изменим в папке ClientApp/src/app файл app.module.ts следующим образом:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';

@NgModule({
    imports: [BrowserModule, FormsModule, HttpClientModule],
    declarations: [AppComponent],
    bootstrap: [AppComponent]
})
export class AppModule { }

Для выполнения запросов и работы с http здесь импортируется модуль HttpClientModule.

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

ASP.NET Core 3 и Angular 9

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

CRUD в ASP.NET Core и Angular

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

Редактирование в ASP.NET Core и Angular

При нажатии на кнопку добавления мы перейдем к форме добавления нового объекта:

Добавление данных в ASP.NET Core и Angular
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850