Взаимодействие React.JS и ASP.NET

Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core

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

В рамках данной статьи продолжим работу с проектом из прошлой темы. На примере ASP.NET MVC 5 создадим простейший интерфейс управления данными. Хотя здесь используется MVC, но все те же действия могут быть легко перенесены и в Web API.

Добавим в проект класс модели Phone, с которой будем работать:

public class Phone
{
    public string Id { get; set; }
    public string Name { get; set; }
    public int Price { get; set; }
}

И изменим код контроллера HomeController:

using ReactApp.Models;
using System;
using System.Collections.Generic;
using System.Web.Mvc;
using System.Linq;

namespace ReactApp.Controllers
{
    public class HomeController : Controller
    {
        static List<Phone> data = new List<Phone>
        {
            new Phone { Id = Guid.NewGuid().ToString(), Name="iPhone 7", Price=52000 },
            new Phone { Id = Guid.NewGuid().ToString(), Name="Samsung Galaxy S7", Price=42000 },
        };
        public ActionResult Index()
        {
            return View();
        }
        public ActionResult GetPhones()
        {
            return Json(data, JsonRequestBehavior.AllowGet);
        }

        [HttpPost]
        public ActionResult AddPhone(Phone phone)
        {
            phone.Id = Guid.NewGuid().ToString();
            data.Add(phone);
            return Json(phone);
        }

        [HttpDelete]
        public ActionResult DeletePhone(string id)
        {
            Phone phone = data.FirstOrDefault(x=>x.Id==id);
            if (phone != null)
            {
                data.Remove(phone);
                return Json(phone);
            }
            return HttpNotFound();
        }
    }
}

В качестве источника данных здесь используется массив объектов Phone, но при необходимости его можно заменить на контекст EF для работы с базой данных.

Метод Index обрабатывает запрос к главной странице. Метод GetPhones() возвращает набор объектов в формате json. Метод AddPhone() обрабатывает post-запросы и выполняет добавление объекта в массив. И метод DeletePhone() удаляет объект из массива по id, обрабатывая запросы DELETE.

ASP.NET MVC 5 имеет одну особенность, которая состоит в том, что по умолчанию запросы PUT, DELETE, OPTIONS отключены. И нам надо их явным образом подключить, если мы хотим использовать их в своем проекте. Для этого в файл конфигурации web.config в узел <system.webServer> надо добавить обработчик:

<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." 
        verb="GET,HEAD,POST,DEBUG,PUT,DELETE,OPTIONS" 
        type="System.Web.Handlers.TransferRequestHandler" 
        preCondition="integratedMode,runtimeVersionv4.0" />

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

<system.webServer>
	<handlers>
		<remove name="Babel" />
		<add name="Babel" verb="GET" path="*.jsx" type="React.Web.BabelHandlerFactory, React.Web" preCondition="integratedMode" />
		<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
		<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." 
             verb="HEAD,POST,DEBUG,PUT,DELETE,OPTIONS" 
             type="System.Web.Handlers.TransferRequestHandler" 
             preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
</system.webServer>

Если мы используем Web API, то там соответственно ничего не надо добавлять в файл конфигурации.

В проекте в папке Scripts определим следующий файл app.jsx:

class Phone extends React.Component{

    constructor(props){
        super(props);
        this.state = {data: props.phone};
        this.onClick = this.onClick.bind(this);
    }
    onClick(e){
        this.props.onRemove(this.state.data);
    }
    render(){
        return <div>
                <p><b>{this.state.data.Name}</b></p>
                <p>Цена {this.state.data.Price}</p>
                <p><button onClick={this.onClick}>Удалить</button></p>
        </div>;
    }
}

class PhoneForm extends React.Component{

    constructor(props){
        super(props);
        this.state = {name: "", price:0};

        this.onSubmit = this.onSubmit.bind(this);
        this.onNameChange = this.onNameChange.bind(this);
        this.onPriceChange = this.onPriceChange.bind(this);
    }
    onNameChange(e) {
        this.setState({name: e.target.value});
    }
    onPriceChange(e) {
        this.setState({price: e.target.value});
    }
    onSubmit(e) {
        e.preventDefault();
        var phoneName = this.state.name.trim();
        var phonePrice = this.state.price;
        if (!phoneName || phonePrice<=0) {
            return;
        }
        this.props.onPhoneSubmit({ name: phoneName, price: phonePrice});
        this.setState({name: "", price:0});
    }
    render() {
        return (
          <form onSubmit={this.onSubmit}>
              <p>
                  <input type="text"
                         placeholder="Модель телефона"
                         value={this.state.name}
                         onChange={this.onNameChange} />
              </p>
            <p>
                <input type="number"
                       placeholder="Цена"
                       value={this.state.price}
                       onChange={this.onPriceChange} />
            </p>
            <input type="submit" value="Сохранить" />
          </form>
        );
    }
}

class PhonesList extends React.Component{

    constructor(props){
        super(props);
        this.state = { phones: [] };

        this.onAddPhone = this.onAddPhone.bind(this);
        this.onRemovePhone = this.onRemovePhone.bind(this);
    }
    // загрузка данных
    loadData() {
        var xhr = new XMLHttpRequest();
        xhr.open("get", this.props.getUrl, true);
        xhr.onload = function () {
            var data = JSON.parse(xhr.responseText);
            this.setState({ phones: data });
        }.bind(this);
        xhr.send();
    }
    componentDidMount() {
        this.loadData();
    }
    // добавление объекта
    onAddPhone(phone) {
        if (phone) {

            var data = new FormData();
            data.append("name", phone.name);
            data.append("price", phone.price);

            var xhr = new XMLHttpRequest();
            xhr.open("post", this.props.postUrl, true);
            xhr.onload = function () {
                if (xhr.status == 200) {
                    this.loadData();
                }
            }.bind(this);
            xhr.send(data);
        }
    }
    // удаление объекта
    onRemovePhone(phone) {

        if (phone) {
            var data = new FormData();
            data.append("id", phone.Id);

            var xhr = new XMLHttpRequest();
            xhr.open("delete", this.props.deleteUrl, true);
            xhr.onload = function () {
                if (xhr.status == 200) {
                    this.loadData();
                }
            }.bind(this);
            xhr.send(data);
        }
    }
    render(){

        var remove = this.onRemovePhone;
        return <div>
                <PhoneForm onPhoneSubmit={this.onAddPhone} />
                <h2>Список смартфонов</h2>
                <div>
                    {
                        this.state.phones.map(function(phone){
                            
                            return <Phone key={phone.Id} phone={phone} onRemove={remove} />
                        })
                    }
                </div>
        </div>;
    }
}

ReactDOM.render(
  <PhonesList getUrl="/home/getphones" postUrl="/home/addphone" deleteUrl="/home/deletephone" />,
  document.getElementById("content")
);

Здесь определен компонент PhonesList, который выводит список объектов. Для вывода каждого отдельного объекта предназначен компонент Phone. Для создания формы добавления нового объекта используется компонент PhoneForm.

Для отправки запросов к серверу здесь применяется стандартный объект XMLHttpRequest. Хотя также можно использовать функции из jquery или какие-то специальные библиотеки.

При рендеринге компоненту PhonesList передается ряд адресов для взаимодействия с сервером. После завершения рендеринга компонента срабатывает метод componentDidMount(), в котором производится загрузка начальных данных.

В методе onAddPhone() получаем пришедший из компонента PhoneForm объект и посылаем его на сервер.

В методе onRemovePhone() получаем пришедший из компонента Phone объект и также посылаем его на сервер.

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

Представление Index.cshtml остается тем же, что и в прошлой теме:

@{
    Layout = null;
}
<html>
<head>
    <title>Hello React</title>
</head>
<body>
    <div id="content"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.2/react.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.2/react-dom.js"></script>
    <script src='@Url.Content("~/Scripts/app.jsx")'></script>
</body>
</html>

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

Использование React JS в ASP.NET MVC 5
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850