React

Using NSwag to Generate React Client for an ASP.NET Core 3 API

This week we are going to add a React project that will utilize the contacts API we created a few weeks ago. This post is part of the revamp of the ASP.NET Core Basics repo that was kicked off when .NET Core 3.0 was released. For details on how the associated sample got to the current point in the application check out the following posts.

Swagger/OpenAPI with NSwag and ASP.NET Core 3
ASP.NET Core 3: Add Entity Framework Core to Existing Project
New Razor Pages Project Backed with an API
Using NSwag to Generate Angular Client for an ASP.NET Core 3 API
Using NSwag to Generate React Client for an ASP.NET Core 3 API
Using NSwag to Generate Blazor Server Client for an ASP.NET Core 3.1 API
Using NSwag to Generate a Vue Client for an ASP.NET Core 3.1 API

I realize that using an ASP.NET Core backed React project for this sample is overkill and a raw React application would have been all that is needed. I chose to use the ASP.NET Core template as a base for all the projects in this series to be consistent. After the initial application creation, you can think of this example as setting up access to a secondary API in addition to the application’s main API if that helps or the generated client on the React side could be used to wrap the API generated by the template.

The sample code before any of the changes in this post can be found here.

Create the React Project

Add a new directory for the React project and then open a terminal set to that directory. The following command can be used to create a new React project. The target framework isn’t required, but I have a preview of .NET Core 3.1 installed and I wanted to make sure this project is targeting .NET Core 3.0.

dotnet new react -f netcoreapp3.0

Next, use the following command to add the new project to the solution file which is in the root of the repo. Your filenames and paths will vary if you are not using the sample code of course.

dotnet sln ..\..\BasicsRefresh.sln add ContactsReact.csproj

Use NSwagStudio to Generate React Client

NSwag provides multiple options for client generation including a CLI, code, and a Windows application. This post is going to use the Windows application which is called NSwagStudio. Download and install NSwagStudio from here.

Next, make sure your API is running and get the URL of its OpenAPI/Swagger specification URL. For example, I am using a local instance of my API and the URL I need is https://localhost:5001/swagger/v1/swagger.json. If you are using the Swagger UI you can find a link to your swagger.json under the API title.

Now that we have the OpenAPI/Swager specification URL for the API switch over to NSwagStudio. The application will open with a new document ready to go. There are a few options we will need to set. First, select the OpenAPI/Swagger Specification tab and enter your API’s specification URL in the Specification URL box.

In the Outputs section check the TypeScript Client checkbox and then select the TypeScript Client tab. There are a lot of options to play with, but I highlighted the options that were important for this sample. First, make sure Module name and Namespace are both empty. I’m sure there is a way to get the client working with a module or namespace, but I didn’t have any luck.   For Template, we just need a Fetch based client. The final option that needs to be set is the Output file path and this is the location you want the generated file to be. I output to the React project directory under ClientApp\src\app\components\contactApi.ts. After all the options are set click Generate Files.

Create UI and Use Generated Client

Once the above is done once you switch back to Visual Studio you should see the following prompt to add the Microsoft.TypeScript.MSBuild NuGet package. The React template doesn’t use TypeScript and NSwag doesn’t have an option to generate a plain JavaScript client so adding this package will allow the build process to take our TypeScript client and convert it to JavaScript. There is an open issue requesting a JavaScript generator.

The sample API is for contact management so the UI we are going to build is to display a contact list. In the ClientApp/src/component directory add a new file named ContactList.js with the following contents. The lines specific to the usage of the NSwag generated client are highlighted.

import React, { Component } from 'react';
import { ContactsClient } from './contactsApi';  

export class ContactList extends Component {
    static displayName = ContactList.name;

    constructor(props) {
        super(props);
        this.state = { contacts: [], loading: true };
    }

    componentDidMount() {
        this.populateContactData();
    }

    static renderContactsTable(contacts) {
        return (
            <table className='table table-striped' aria-labelledby="tabelLabel">
                <thead>
                    <tr>
                        <th>Name</th>
                        <th>Address</th>
                        <th>City</th>
                        <th>State</th>
                        <th>Postal Code</th>
                        <th>Phone</th>
                        <th>Email</th>
                    </tr>
                </thead>
                <tbody>
                    {contacts.map(contact =>
                        <tr key={contact.id}>
                            <td>{contact.name}</td>
                            <td>{contact.address}</td>
                            <td>{contact.city}</td>
                            <td>{contact.state}</td>
                            <td>{contact.postalCode}</td>
                            <td>{contact.phone}</td>
                            <td>{contact.email}</td>
                        </tr>
                    )}
                </tbody>
            </table>
        );
    }

    render() {
        let contents = this.state.loading
            ? <p><em>Loading...</em></p>
            : ContactList.renderContactsTable(this.state.contacts);

        return (
            <div>
                <h1 id="tabelLabel" >Contacts</h1>
                {contents}
            </div>
        );
    }

    async populateContactData() {
        let client = new ContactsClient();
        client.getContacts()
              .then(data => this.setState({ contacts: data, loading: false }));
    }
}

As you can see from the populateContactData code above we are creating a new instance of the ContactsClient and calling its getContacts function and using the data we get back from the API to set the state of the component with the data return from the API.

Now that the contact list is ready it needs a link in the navbar. First, in the App.js file, we need to add the contact list to the router. The following is the full file with the added lines highlighted.

import React, { Component } from 'react';
import { Route } from 'react-router';
import { Layout } from './components/Layout';
import { Home } from './components/Home';
import { FetchData } from './components/FetchData';
import { Counter } from './components/Counter';
import { ContactList } from './components/ContactList';

import './custom.css'

export default class App extends Component {
  static displayName = App.name;

  render () {
    return (
      <Layout>
        <Route exact path='/' component={Home} />
        <Route path='/contacts' component={ContactList} />
        <Route path='/counter' component={Counter} />
        <Route path='/fetch-data' component={FetchData} />
      </Layout>
    );
  }
}

Now to make to add a Contacts link to the navbar open the NavMenu.js file and add the following to the with the other nav items.

<NavItem>
    <NavLink tag={Link} className="text-dark" to="/contacts">Contacts</NavLink>
</NavItem>

Wrapping  Up

I had a bit more trouble getting the NSwag client working this round, but that was more due to my shallow knowledge with React than a problem with NSwag.

The sample projects after all the changes in this post can be found here.

Using NSwag to Generate React Client for an ASP.NET Core 3 API Read More »

ASP.NET Core 3: React Template with Auth

Preview 3 of ASP.NET Core was released on March 6th. This release added the option to include auth when creating an Angular or React application using the templates provided by Microsoft. I can’t convey how happy this feature makes me. As someone that hasn’t done a super deep dive on auth having a good starting point for a new application is very helpful.

Installation

To get the updated version of the templates install the latest version of the .NET Core 3 previews. You can find the installers here.

Project Creation

Using the .NET CLI from a command prompt in the directory you want the project created in run the following command.

dotnet new react --auth Individual

After the project is built you should be able to use the following command to run the application.

dotnet run

Issues with Preview 3

I did the above and it results in the following error.

Microsoft.AspNetCore.SpaServices: Information:

Failed to compile.

info: Microsoft.AspNetCore.SpaServices[0]
./src/components/api-authorization/ApiAuthorizationConstants.js
It seems that there are a few issues with the React template that shipped with Preview 3.  To fix the above error open the ApiAuthorizationConstants.js file found in the ClientApp/src/components/api-authorization directory and make the following change.
Before:
ApiAuthorizationPrefix = prefix,

After:
ApiAuthorizationPrefix: prefix,

After that fix, you will see the following error.

./src/components/api-authorization/ApiAuthorizationRoutes.js
Module not found: Can’t resolve ‘./components/api-authorization/Login’ in ‘\ClientApp\src\components\api-authorization’

This fix is a bit more involved. I found the workaround in the known issues page provided by Microsoft.

First, delete the ApiAuthorizationRoutes.js file which is in the same directory as the previous fix. Then replace the contents of App.js found in the ClientApp/src directory with the following.

import React, { Component } from 'react';
import { Route } from 'react-router';
import { Layout } from './components/Layout';
import { Home } from './components/Home';
import { FetchData } from './components/FetchData';
import { Counter } from './components/Counter';
import { Login } from './components/api-authorization/Login'
import { Logout } from './components/api-authorization/Logout'
import AuthorizeRoute from './components/api-authorization/AuthorizeRoute';
import { ApplicationPaths, LoginActions, LogoutActions } from './components/api-authorization/ApiAuthorizationConstants';

export default class App extends Component {
  static displayName = App.name;

  render () {
    return (
      <Layout>
        <Route exact path='/' component={Home} />
        <Route path='/counter' component={Counter} />
        <AuthorizeRoute path='/fetch-data' component={FetchData} />
        <Route path={ApplicationPaths.Login} render={() => loginAction(LoginActions.Login)} />
        <Route path={ApplicationPaths.LoginFailed} render={() => loginAction(LoginActions.LoginFailed)} />
        <Route path={ApplicationPaths.LoginCallback} render={() => loginAction(LoginActions.LoginCallback)} />
        <Route path={ApplicationPaths.Profile} render={() => loginAction(LoginActions.Profile)} />
        <Route path={ApplicationPaths.Register} render={() => loginAction(LoginActions.Register)} />
        <Route path={ApplicationPaths.LogOut} render={() => logoutAction(LogoutActions.Logout)} />
        <Route path={ApplicationPaths.LogOutCallback} render={() => logoutAction(LogoutActions.LogoutCallback)} />
        <Route path={ApplicationPaths.LoggedOut} render={() => logoutAction(LogoutActions.LoggedOut)} />
      </Layout>
    );
  }
}

function loginAction(name){
    return (<Login action={name}></Login>);
}

function logoutAction(name) {
    return (<Logout action={name}></Logout>);
}

With the above fixes, the site will load and you should see something like the following.

When you try to register you will get the following error.

MissingMethodException: Method not found: ‘Microsoft.EntityFrameworkCore.Metadata.Builders.IndexBuilder Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder`1.HasIndex(System.Linq.Expressions.Expression`1<System.Func`2<!0,System.Object>>)’.

IdentityServer4.EntityFramework.Extensions.ModelBuilderExtensions+<>c__DisplayClass2_0.<ConfigurePersistedGrantContext>b__0(EntityTypeBuilder<PersistedGrant> grant)

Again the known issue page to the rescue. Open your csproj file and replace the following package references.

Before:
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.0.0-preview3-19153-02" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.0.0-preview3.19153.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.0.0-preview3.19153.1" />

After:
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.0.0-preview-18579-0056" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.0.0-preview.19080.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.0.0-preview.19080.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.0.0-preview.19080.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.0.0-preview.19080.1" />

And add the following bit of XML.

<PropertyGroup>
  <NoWarn>$(NoWarn);NU1605</NoWarn>
</PropertyGroup>

Wrapping Up

After the above tweaks, everything just worked. I’m sure by preview 4 the experience will be even better. Even with the issues I hit getting a basic new project going I am very excited. Thank you, ASP.NET team, at Microsoft adding auth to these templates is going to be super helpful. Also, note that Angular template also got an auth option (and it seems to be a smoother experience at the moment).

ASP.NET Core 3: React Template with Auth Read More »

React: Form for Contact Add

Last week’s post covered adding a read-only view of a contact’s details. As before the goal is to get the React project’s features in line with the Aurelia and Angular samples. This week we will be adding a form to all addition of a new contact. The code before any changes can be found here.

Contact List

On the contact list page, we need to add a link to create a contact. This will be added just after the header in the ContactList.tsx file.

<h1>Contact List</h1>
<Link to={'contactdetail'}>Create New Contact</Link>

Routing

In order to stay in line with the other sample, the ContactDetail component is going to be handling both the read-only view and the view to add a contact. This means the ID that is currently part of the contact detail needs to be optional. The following is the change to make ID optional by adding a question mark.

Before:
<Route path='/contactdetail/:id' component={ContactDetail} />

After:
<Route path='/contactdetail/:id?' component={ContactDetail} />

Contact Service

In the ContactService class, we need to add a save function. This new function will make a post request to the contacts API and return the new contact with the ID from the API to the caller.

save(contact: Contact): Promise<Contact> {
    return fetch(this.baseUrl,
            {
                method: 'post',
                headers: {
                    'Accept': 'application/json',
                    'Content-Type': 'application/json' 
                },
                body: JSON.stringify(contact)
            })
        .then(response => response.json())
        .then(contact => new Contact(contact));
}

Contact Detail

The ContactDetail class is where most of the changes are. I had some trouble getting React’s forms to work directly with the instance of the contact class and ended up just storing the parts of a contact directly in state instead of as an object. I expect this is a failure on my part and not an issue with React. I may revisit this in the future. Below is the new structure of the state used by contact details.

interface ContactDetailState {
    id: string;
    name: string;
    address: string;
    city: string;
    state: string;
    postalCode: string;
    phone: string;
    email: string;
    loading: boolean;
    redirect: boolean;
}

Next, the constructor needed to be changed to handle the new state structure and to handle being called without an ID.

constructor(props: any) {
    super();

    if (props.match.params.id == undefined) {
        this.state = {
            id: props.match.params.id,
            name: '', address: '', city: '',
            state: '', postalCode: '', phone: '',
            email: '',
            loading: false,
            redirect: false
        };
    }
    else {
        this.state = {
            id: props.match.params.id,
            name: '', address: '', city: '',
            state: '', postalCode: '', phone: '',
            email: '',
            loading: true,
            redirect: false
        };

        let contactService = new ContactService();
        contactService.getById(this.state.id)
            .then(data => {
                this.setState({
                    name: data.name, address: data.address, city: data.city,
                    state: data.state, postalCode: data.postalCode, phone: data.phone,
                    email: data.email,
                    loading: false
                });
            });
    }
}

Again, this is way more code than it would be the contact class were being used. Next, the render function needs to be adjusted to render the UI for the read-only view or the contact creation. The decision is based on the ID being set or undefined.

public render() {
    let contents = this.state.loading
        ? <p><em>Loading...</em></p>
        : this.state.id != undefined &&
          !this.state.redirect
            ? this.renderExistingContact()
            : this.renderNewContact();

    return <div>
        <h1>Contact Detail</h1>
        <hr />
        {contents}
        <NavLink to={'/contactlist'}>Back to List</NavLink>
        <hr />
    </div>;
}

The render of an existing contact needs to be changed to use state instead of an instance of a contact.

private renderExistingContact() {
    return <dl className="dl-horizontal">
        <dt>ID</dt>
        <dd>{this.state.id}</dd>
        <dt>Name</dt>
        <dd>{this.state.name}</dd>
        <dt>Address</dt>
        <dd>{this.state.address} {this.state.city}, {this.state.state} {this.state.postalCode}</dd>
        <dt>Phone</dt>
        <dd>{this.state.phone}</dd>
        <dt>Email</dt>
        <dd>{this.state.email}</dd>
    </dl>;
}

The following is the render of the add contact UI. I will call out a couple of parts after. The bulk of the code is just rending of the form.

    private renderNewContact() {
        return (
            <div>
                {this.state.redirect && <Redirect to={`/contactdetail/${this.state.id}`}/>}
                <form role="form" className="form-horizontal" onSubmit={(e: any) => this.handleSubmit(e)}>
                    <div className="form-group">
                        <label className="col-sm-2 control-label">Name</label>
                        <div className="col-sm-10">
                            <input type="text" placeholder="name" className="form-control" name="name" value={this.state.name} onChange={(e: any) => this.handleChange(e)} />
                        </div>
                    </div>
                    <div className="form-group">
                        <label className="col-sm-2 control-label">Address</label>
                        <div className="col-sm-10">
                            <input type="text" placeholder="address" className="form-control" name="address" value={this.state.address} onChange={(e: any) => this.handleChange(e)} />
                        </div>
                    </div>
                    <div className="form-group">
                        <label className="col-sm-2 control-label">City</label>
                        <div className="col-sm-10">
                            <input type="text" placeholder="city" className="form-control" name="city" value={this.state.city} onChange={(e: any) => this.handleChange(e)} />
                        </div>
                    </div>
                    <div className="form-group">
                        <label className="col-sm-2 control-label">State</label>
                        <div className="col-sm-10">
                            <input type="text" placeholder="state" className="form-control" name="state" value={this.state.state} onChange={(e: any) => this.handleChange(e)} />
                        </div>
                    </div>
                    <div className="form-group">
                        <label className="col-sm-2 control-label">Zip</label>
                        <div className="col-sm-10">
                            <input type="text" placeholder="zip" className="form-control" name="postalCode" value={this.state.postalCode} onChange={(e: any) => this.handleChange(e)} />
                        </div>
                    </div>
                    <div className="form-group">
                        <label className="col-sm-2 control-label">Phone</label>
                        <div className="col-sm-10">
                            <input type="text" placeholder="phone" className="form-control" name="phone" value={this.state.phone} onChange={(e: any) => this.handleChange(e)} />
                        </div>
                    </div>
                    <div className="form-group">
                        <label className="col-sm-2 control-label">Email</label>
                        <div className="col-sm-10">
                            <input type="email" placeholder="email" className="form-control" name="email" value={this.state.email} onChange={(e: any) => this.handleChange(e)} />
                        </div>
                    </div>
                    <div className="text-center">
                        <button className="btn btn-success btn-lg" type="submit">Save</button>
                        <button className="btn btn-danger btn-lg" onClick={() => this.reset()}>Reset</button>
                    </div >
                </form>
            </div>
        );
    }

The following is the input for the contact’s name.

<input type="text" placeholder="name" className="form-control" name="name" value={this.state.name} onChange={(e: any) => this.handleChange(e)} />

Since I decided to go with the controlled component route React will be responsible for being the source of truth, not the form its self. To accomplish this it is important that the input has a name and an onChange event handler set up. The following is the handleChange function which uses the name from the on change event to update the proper property in the component’s state.

private handleChange(event: any): void {
    const target = event.target;
    const value = target.value;
    const name = target.name;

    this.setState({ [name]: value });
}

The following line is the reset button which will reset the state to blank out all the fields in the form.

<button className=”btn btn-danger btn-lg” onClick={() => this.reset()}>Reset</button>

The reset function just uses setState to blank out all the fields.

private reset() {
    this.setState({
        name: '', address: '', city: '',
        state: '', postalCode: '', phone: '',
        email: ''
    });
}

Not surprisingly the submit button triggers a submit of the form. What happens on submit is defined in the opening form tag.

<form role="form" className="form-horizontal" onSubmit={(e: any) => this.handleSubmit(e)}>

The handleSubmit function takes the contact information in state and uses it to create a new instance of a Contact which is then passed to the ContactService to be saved. The service returns a new contact object from the server and the ID is stored to state.

handleSubmit(event: any): void {
    event.preventDefault();

    let contact = new Contact();
    contact.name = this.state.name;
    contact.address = this.state.address;
    contact.city = this.state.city;
    contact.state = this.state.state;
    contact.postalCode = this.state.postalCode;
    contact.phone = this.state.phone;
    contact.email = this.state.email;

    let contactService = new ContactService();
    contactService.save(contact)
        .then(c => this.setState({ id: String(c.id) }));

    if (this.state.id) {
        this.setState({ redirect: true });
    }
}

If the server does return an ID for the new contact then the redirect is set to true. Then will case the following code to run in renderNewContact which will redirect the user back to the read-only view of the new contact.

{this.state.redirect && <Redirect to={`/contactdetail/${this.state.id}`}/>}

Wrapping Up

This pretty much gets the React application in line with the Aurelia and Angular sample applications. It has been fun getting a handle on the very, very basics of React. While I am back in the basics sample projects I may go ahead and tackle a Vue sample next.

The finished code can be found here.

React: Form for Contact Add Read More »

React: Contact Detail

Last week’s post covered adding a React project to the ASP.NET Core Basics solution. As I stated last week the goal is to get the React project’s features in line with the Aurelia and Angular samples. This week we will be adding a read-only view of a contact’s details. The code before any changes can be found here.

Contact Class

The Contact interface in the ContactList.tsx should be deleted and in its please we will add a Contact class. As part of this change, I also moved the contact related items to a contact directory. Add a contact.ts file to the ClientApp/components/contacts/ directory with the following contents.

export class Contact {
    id: number;
    name: string;
    address: string;
    city: string;
    state: string;
    postalCode: string;
    phone: string;
    email: string;

    constructor(data?: any) {
        if (data == null) return;
        (<any>Object).assign(this, data);
    }

    getAddress(): string {
        return `${this.address} ${this.city}, ${this.state} ${this.postalCode}`;
    }

}

This class is very simple and will be used later to show how a function call can be used as part of rendering.

Contact Service

Since the application will now have two places that need to access the ASP.NET Core API I decided to refactor the API access behind a service. This is the same style used by the Aurelia and Angular applications. Add a contactService.ts file to the Contacts directory. The service will provide functions to get all contacts or a single contact using its ID. The following is the full class.

import 'isomorphic-fetch';
import { Contact } from './contact';

export class ContactService {

    private baseUrl = 'http://localhost:13322/api/contactsApi/';

    getAll(): Promise<Contact[]> {
        return fetch(this.baseUrl)
            .then(response => response.json() as Promise<Contact[]>)
            .then(contacts => Array.from(contacts, c => new Contact(c)));
    }

    getById(id: string): Promise<Contact> {
        return fetch(`${this.baseUrl}${id}`)
            .then(response => response.json())
            .then(contact => new Contact(contact));
    }

}

The final step in this refactor is to use the new service in the ContactList class. First, add imports for the service and Contact class.

import { Contact } from './contact';
import { ContactService } from './contactService';

Then, replace the fetch call with the service.

Before:
fetch('http://localhost:13322/api/contactsApi/')
    .then(response => response.json() as Promise<Contact[]>)

After:
let contactService = new ContactService();
contactService.getAll()

Contact Detail Component

Add a ContactDetail.tsx file which will be used to show the details of a contact including using the getAddress function of the Contact class. The following is the full contents of the file.

import * as React from 'react';
import { RouteComponentProps } from 'react-router';
import { Link, NavLink } from 'react-router-dom';
import 'isomorphic-fetch';
import { Contact } from './contact';
import { ContactService } from './contactService';

interface ContactDetailState {
    id: string;
    contact: Contact | undefined;
    loading: boolean;
}

export class ContactDetail extends React.Component<RouteComponentProps<{}>, ContactDetailState> {

    constructor(props: any) {
        super();
        this.state = { id: props.match.params.id, contact: undefined, loading: true };

        let contactService = new ContactService();
        contactService.getById(this.state.id)
            .then(data => {
                this.setState({ contact: data, loading: false });
            });
    }

    public render() {
        let contents = this.state.loading
            ? <p><em>Loading...</em></p>
            : this.state.contact
                ? ContactDetail.renderContactsTable(this.state.contact)
                : <p>No contacts</p>;

        return <div>
            <h1>Contact Detail</h1>
            <hr />
            {contents}
            <NavLink to={'/contactlist'}>Back to List</NavLink>
            <hr />
        </div>;
    }

    private static renderContactsTable(contact: Contact) {
        return <dl className="dl-horizontal">
                   <dt>ID</dt>
                   <dd>{contact.id}</dd>
                   <dt>Name</dt>
                   <dd>{contact.name}</dd>
                   <dt>Address</dt>
                   <dd>{contact.getAddress()}</dd>
                   <dt>Phone</dt>
                   <dd>{contact.phone}</dd>
                   <dt>Email</dt>
                   <dd>{contact.email}</dd>
               </dl>;
    }
}

This is all very similar to the things I have covered before. Now that we have a contact detail component we need a way to display it.

Routing with a Parameter

Import the contact detail in the route.tsx file.

import { ContactDetail } from './components/contacts/ContactDetail';

Next, add a route path for contact detail that expects an ID.

<Route path='/contactdetail/:id' component={ContactDetail} />

Back in the ContactList component change the ID to be a link to the new contact detail route using the ID of the contact.

Before:
<td>{contact.id}</td>

After:
<td><Link to={`contactdetail/${contact.id}`}>{contact.id}</Link></td>

The code for pulling the route parameter was in the ContactDetail component above, but I am going to show it again just so all the route with parameter information is together. The route parameters can be accessed using props.match.params.{parameter name} which in this case ends up being props.match.params.id. The following is the constructor of the ContactDetail component which is using a route parameter.

constructor(props: any) {
    super();
    this.state = { id: props.match.params.id, contact: undefined, loading: true };

    let contactService = new ContactService();
    contactService.getById(this.state.id)
        .then(data => {
            this.setState({ contact: data, loading: false });
        });
}

Wrapping Up

This brings the projects one step closer to being on the same level feature-wise. I expect at least one more post to get the project features lined up so make sure and keep a lookout for the next post.

Keep in mind that this is my first look at React and my examples may or may not be idiomatic. So far I am really enjoying working with React.

The code in a finished state can be found here.

React: Contact Detail Read More »

ASP.NET Core Basics: React with an API

In the past, I have done some exploration on Aurelia and Angular via the ASP.NET Core Basics series. This post is going to take a similar approach as I start doing some exploration with React. The code for the project will be in the same repo as the previous basics examples and will be utilizing the same API to pull data. The code before adding the React project can be found here.

This post is going to cover adding a React project to the existing using the React template that is now built into Visual Studio. The same thing can be accomplished using the .NET CLI so don’t feel like Visual Studio is required. The goal for the React project in this initial post will be to connect to the contacts API and download a list of contacts and render that to the screen. In future posts, I hope to expand this functionality to match that of the Aurelia and Angular projects.

Project Creation

Right-click and select Add > New Project.

In the Add New Project dialog select the ASP.NET Core Web Application. In the case of the sample, the project will be named React. Click OK to continue.

On the next screen make sure and select ASP.NET Core 2.0 and the React.js template. Then click OK.

The following is the resulting React project in the context of the full solution.

Next, make sure and run npm install from a command prompt in the React project’s directory to ensure all the npm packages get restored.

Adding the Contact List

Inside the ClientApp/components/ directory add a file name ContactList.tsx. TSX is the TypeScript version of the React JSX file type. The official docs on JSX can be found here. Since this is my first time working with React I took the FetchData.tsx file and copied the contents and used that as the starting point for my contact list. To lead with there is an interface for what should define a contact.

interface Contact {
    id: number;
    name: string;
    address: string;
    city: string;
    state: string;
    postalCode: string;
    phone: string;
    email: string;
}

Next, we have an interface for the state of this component with contains a loading flag and an array of contacts.

interface ContactListState {
    contacts: Contact[];
    loading: boolean;
}

In the constructor for the component is where the data is pulled from the API using fetch. The data from the API is then saved to the state of the component using the setState function.

constructor() {
    super();
    this.state = { contacts: [], loading: true };

    fetch('http://localhost:13322/api/contactsApi/')
        .then(response => response.json() as Promise<Contact[]>)
        .then(data => {
            this.setState({ contacts: data, loading: false });
        });
}

Next, the component has a function named renderContactsTable which takes an array of contacts and returns how they should be rendered. In this case, the contacts are rendered to a table that displays the contact ID and Name.

private static renderContactsTable(contacts: Contact[]) {
    return <table className='table'>
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
            </tr>
        </thead>
        <tbody>
            {contacts.map(contact =>
                <tr key={contact.id}>
                    <td>{contact.id}</td>
                    <td>{contact.name}</td>
                </tr>
            )}
        </tbody>
    </table>;
}

Finally, there is the render function. As you can guess this is what gets called to render the component. In this case, either “Loading” or the contact list gets displayed depending on if the contact list data has been loaded or not.

public render() {
    let contents = this.state.loading
        ? <p><em>Loading...</em></p>
        : ContactList.renderContactsTable(this.state.contacts);

    return <div>
        <h1>Contact List</h1>
        {contents}
    </div>;
}

The following is the full file for reference.

import * as React from 'react';
import { RouteComponentProps } from 'react-router';
import 'isomorphic-fetch';

interface ContactListState {
    contacts: Contact[];
    loading: boolean;
}

export class ContactList extends React.Component<RouteComponentProps<{}>, ContactListState> {
    constructor() {
        super();
        this.state = { contacts: [], loading: true };

        fetch('http://localhost:13322/api/contactsApi/')
            .then(response => response.json() as Promise<Contact[]>)
            .then(data => {
                this.setState({ contacts: data, loading: false });
            });
    }

    public render() {
        let contents = this.state.loading
            ? <p><em>Loading...</em></p>
            : ContactList.renderContactsTable(this.state.contacts);

        return <div>
            <h1>Contact List</h1>
            {contents}
        </div>;
    }

    private static renderContactsTable(contacts: Contact[]) {
        return <table className='table'>
            <thead>
                <tr>
                    <th>ID</th>
                    <th>Name</th>
                </tr>
            </thead>
            <tbody>
                {contacts.map(contact =>
                    <tr key={contact.id}>
                        <td>{contact.id}</td>
                        <td>{contact.name}</td>
                    </tr>
                )}
            </tbody>
        </table>;
    }
}

interface Contact {
    id: number;
    name: string;
    address: string;
    city: string;
    state: string;
    postalCode: string;
    phone: string;
    email: string;
}

Add Contact List to Navigation

Now that we have the contact list component it needs to be added to the navigation menu. The first step is to add it to the application’s router. This can be found in the routes.tsx file. The file is short so I am going to include the full content. Lines 7 and 13 are the ones added to handle our contact list.

import * as React from 'react';
import { Route } from 'react-router-dom';
import { Layout } from './components/Layout';
import { Home } from './components/Home';
import { FetchData } from './components/FetchData';
import { Counter } from './components/Counter';
import { ContactList } from './components/ContactList';

export const routes = <Layout>
    <Route exact path='/' component={Home} />
    <Route path='/counter' component={Counter} />
    <Route path='/fetchdata' component={FetchData} />
    <Route path='/contactlist' component={ContactList} />
</Layout>;

The last change is to add a navigation link to the NavMenu found in the NavMenu.tsx file. As I am sure most of us are used to adding an item to the nav menu is just adding a new li, but with the React specific NavLink bit.

<li>
  <NavLink to={'/contactlist'} activeClassName='active'>
      <span className='glyphicon glyphicon-th-list-alt'></span> Contact List
  </NavLink>
</li>

Wrapping Up

React is different than both Aurelia and Angular. Don’t take that as a good or bad thing. I don’t plan to pick on a side on the Angular vs React debate I just want to get a good feel for the different frameworks. So far the React experience has been pretty nice and I look forward to doing more exploration.

You can find the finished code for this post here.

ASP.NET Core Basics: React with an API Read More »