I command you…
If the last lesson was all about representing queries in our application (“please give me this data”) this one is all about issuing commands.
Commands generally change data in your application and execute key business logic.
The obvious command for us to build first is one for posting new tweets.
But, hold your horses, you may have noticed we don’t actually have a “store” for our tweets yet; the query we added last time just returns hardcoded data.
We could go ahead and bring in an actual database at this point, maybe utilising something like EF Core, but our focus is to figure out how to model commands and queries, and hook them up to a front end (React.js in this case) so we’ll take a pragmatic approach and use a fake “in memory” data store so we can keep moving forward.
To create a fake data store for tweets we really just need to hold a collection of them in memory somewhere. We can then read from, and add to this collection and it will act as a proxy for a real database at some point down the line.
Add this Store.cs class somewhere in your application (a Data folder at the root of your application looks like as good a place as any).
using System.Collections.Generic;
using System.Linq;
namespace MyTweets.Data
{
public class Store
{
public IList<Tweet> Tweets { get; set; } = new List<Tweet>
{
new Tweet
{
Contents = "One from the backend"
}
};
public void Add(Tweet tweet)
{
Tweets.Add(tweet);
}
}
public class Tweet
{
public string Contents { get; set; }
}
}
So this is really just a model (Tweet
) and a collection to store Tweets
in. This class also exposes a method for adding a new tweet.
If you’ve used Entity Framework Core you’ll be familiar with the DbContext
. This Store
class is essentially our pretend DBContext
and is merely here to give us somewhere to “store” our tweets.
To use this “store” in our application we’ll want to register it with the Microsoft Dependency Injection framework (thus making it available to be injected into any class in our application).
Add the following to ConfigureServices
in Startup.cs.
services.AddSingleton<Store>();
(you’ll need to add a using statement: using MyTweets.Data;
)
With this in place, our application will hold one instance of this Store and forward all requests to it. This will suffice for testing our application but it’s worth reiterating; you’d want to swap this out for a real database for anything that’s actually going to be used by real people!
Update List.cs to fetch from the new store
We have a store but we’re still retrieving hardcoded tweets at the moment when we run our List
query. Let’s switch that over to use the new store.
Replace the contents of QueryHandler
in List.cs with this…
public class QueryHandler : IRequestHandler<Query, Model>
{
private Store _store;
public QueryHandler(Store store)
{
_store = store;
}
public async Task<Model> Handle(Query request, CancellationToken cancellationToken)
{
return new Model
{
Tweets = _store.Tweets
.Select(x => x.Contents)
.ToList()
};
}
}
We’ve used ASP.NET Core’s dependency injection to bring in our Tweet store
and map the Contents
of each tweet into our Model (which, if you recall is simply a list of strings).
It’s all in the command
Now we need to create our command to post a new tweet.
Realistically this will take in the tweet contents, then add it to our store. Unlike Queries, Commands don’t necessarily return in a response.
We can model this in a very similar way to our earlier Query…
Add a Post.cs class to Features/Tweets and replace the contents with this…
using MediatR;
using System.Threading;
using System.Threading.Tasks;
using MyTweets.Data;
namespace MyTweets.Features.Tweets
{
public class Post
{
public class Command : IRequest
{
public string Text { get; set; }
}
}
}
This is our command. All we need (for now) are the contents of the tweet itself.
Now we can add a handler, just below the Command
class (but still nested inside the Post
class).
public class CommandHandler : AsyncRequestHandler<Post.Command>
{
private readonly Store store;
public CommandHandler(Store store)
{
this.store = store;
}
protected override async Task Handle(Post.Command request, CancellationToken cancellationToken)
{
store.Add(new Tweet { Contents = request.Text });
}
}
Because this is a command and doesn’t need to return data, I’ve opted to implement AsyncRequestHandler
instead of IRequestHandler
. The main difference being AsyncRequestHandler
doesn’t require us to return a response.
Call “Post” from our controller
Now we can add a new action to our TweetController. Just below the existing List
method will do.
[HttpPost]
public async Task<IActionResult> Post([FromBody] Post.Command command)
{
var model = await mediator.Send(command);
return Ok(model);
}
Note how similar this is to the List
method. As far as MediatR is concerned this is no different, we’re simply “sending” an instance of our request.
The main change from our perspective is that we’re letting ASP.NET populate our Post.Command
from the incoming request, then handing this over to MediatR (which will locate and execute our handler).
A little bit of UI goes a long way
Now we just need to “hook this up” to the front end.
For that we’ll want some mechanism to type out our new Tweet and “post” it.
Head over to the components/Tweets/ folder and add a new javascript file called PostNew.js.
Replace its contents with the following…
import React from 'react';
export default class PostNew extends React.Component {
state = {text: ''};
async handleSubmit(event) {
alert('boo');
event.preventDefault();
}
handleChange(event) {
this.setState({text: event.target.value});
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<textarea className="form-control" value={this.state.text} onChange={this.handleChange}/>
</div>
<button type="submit" className="btn btn-primary">
Tweet it!
</button>
</form>
)
}
}
Aside from a little Bootstrap, we’re using standard HTML elements here.
The interesting parts are the two functions; handleSubmit
and handleChange
.
First handleChange
handles the text area’s onChange
event. When this fires (because the textarea’s value changes) handleChange
kicks in, looks for the new value (from event.target.value
) and uses it to update our component’s state with the entered value.
Whenever we type something into the text area, the text
value in state will be updated based on what we typed in.
We also need to handle the form submitted event. handleSubmit
does this and, for now, simply throws an alert in the browser stating “boo”.
The joy of “this”
Sadly, javascript being javascript, this code will error in the browser.
Give it a spin and you’ll most likely see this error…
So what’s this all about?
Well this
in javascript is a little… confusing, and has a tendency to refer to different things at different times.
The problem here is that this
has lost its binding, and fallen back to the default which (in this case) is undefined.
Why this happens is a little beyond the scope of this short course, but has to do with the fact that the onChange
event is raised from a textarea
control, and this
is bound to a value based on where the event is raised, not our handling function.
But, fear not, we can wrestle back control of this
and make it do what we actually want.
For this we’ll need to add a constructor. Add this code somewhere near the top of your PostNew.js component.
constructor(props){
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
When we add a constructor to a React component we have to manually forward props on to the React.Component
base class, hence the call to super(props
).
Now we manually bind handleChange
to this
in our constructor. At this point, in our constructor, this
refers to our component class itself.
I’ve also done the same for handleSubmit
as we’re about to need that too.
this
is pretty confusing eh?!
But, the key takeaway is, bind this
to your functions (in the constructor) and you’ll find this
referring to the class you’re in. Test this in the browser now; everything should render without errors, and if you click the button you should see “boo” in an alert!
Call the backend API
So finally, to make this work, we need to update handleSubmit
to actually call our API, including the tweet’s text.
Replace the contents of handleSubmit
with this…
async handleSubmit(event) {
event.preventDefault();
const options = {
method: 'POST',
body: JSON.stringify(this.state),
headers: { 'Content-Type': 'application/json' }
};
await fetch('Tweet', options);
}
This uses fetch
but this time to initiate a POST
to our API, which should then grab the value of text
and use it to add a new Tweet to our temporary Tweet store.
Time to tweet
So we have a PostNew
component but we’re not showing it anywhere (yet).
You can show this component wherever you want but for now I’ll render it in List.js.
Change render()
to include an instance of our PostNew
component as follows…
render() {
return (
<>
<h3>Tweets</h3>
<PostNew/>
{this.state.tweets.map(tweet => <Tweet text={tweet}/>)}
</>
);
}
With this in place launch your application in the browser and try posting a new tweet.
The tweet should be added to our store; refresh the page to see it appear in the list of tweets!
We’re nearly there, but that last step (refreshing to see the new tweet) feels a little, well, clunky…
A better option would be to trigger a refresh of the tweets when a new one is posted.
That’s what we’ll cover in the next and final lesson (as well as a quick recap on what we’ve seen plus some bonus tasks for you to have a stab at yourself).