In a previous post I stated that the EWL implementation of RealWorld contains 60-80% less code than its React + ASP.NET Core counterpart. Now I’ll show you why.
As developers, let us imagine implementing a new feature in RealWorld. On the article page, we’ll add a Flag as Spam
button for readers. We’ll show the button to everyone except the author. And we’ll back this feature in the database with an IsSpam
boolean column in the Articles
table.
Step 1: Add the database column
In ASP.NET Core
We first add a new property to our Article
entity class:
public bool IsSpam { get; set; }
Entity Framework Core (our data-access layer) will see this property when creating the database and include it as a column in the Articles
table.
Missing piece: The project lacks database-migration infrastructure, meaning that we cannot add the IsSpam
column to an existing database or specify a value (i.e. true
or false
) for existing article rows. Entity Framework Core does support migrations but they are not configured in the project.
We also add a line in the create-article request handler to set the column value for new articles:
IsSpam = false
In EWL
First, we append these lines to Database Updates.sql
:
alter table Articles add IsSpam bit null /* add nullable column */
update Articles set IsSpam = 0 /* set value for existing articles */
alter table Articles alter column IsSpam bit not null /* make column non-nullable */
We then run Update-DependentLogic
in the Package Manager Console, which will detect these new lines in the script and execute them. Finally we add a line to the Editor page to set the value for every new article:
mod.IsSpam = false;
This procedure isn’t much different from its ASP.NET Core counterpart above, aside from being more complete in terms of database migration. But keep reading to see some real divergence.
Step 2: Modify the web API
In ASP.NET Core
Our main task here is to add a new request handler that flags an article as spam:
public class MarkSpam {
public class Command : IRequest {
public Command(string slug) {
Slug = slug;
}
public string Slug { get; set; }
}
public class CommandValidator : AbstractValidator<Command> {
public CommandValidator() {
RuleFor(x => x.Slug).NotNull().NotEmpty();
}
}
public class QueryHandler : IRequestHandler<Command> {
private readonly ConduitContext _context;
public QueryHandler(ConduitContext context) {
_context = context;
}
public async Task<Unit> Handle(Command message, CancellationToken cancellationToken) {
var article = await _context.Articles.FirstOrDefaultAsync(x => x.Slug == message.Slug, cancellationToken);
if (article == null) {
throw new RestException(HttpStatusCode.NotFound, new { Article = Constants.NOT_FOUND });
}
article.IsSpam = true;
await _context.SaveChangesAsync(cancellationToken);
return Unit.Value;
}
}
}
We also need to hook into this handler from a new method in ArticlesController
:
[HttpPatch("{slug}")]
[Authorize(AuthenticationSchemes = JwtIssuerOptions.Schemes)]
public async Task MarkSpam(string slug) {
await _mediator.Send(new MarkSpam.Command(slug));
}
This causes HTTP PATCH
requests on an article to flag it as spam.
Security issue: This web API lacks any kind of user authorization (!), so I won’t include that piece of the feature. This type of mistake is not possible with EWL since the authorization is coupled to the visibility of the button in the UI (see step 3).
In EWL
Grab a coffee and relax: there is no Web API! See my essay about this.
Step 3: Create the button
In React
Before working on the button, we need to make the current user available to the component that will contain the button. This is important because we aren’t going to make the button available to the author of the article. In the article-index component, we forward the user object down to ArticleMeta
:
<ArticleMeta currentUser={this.props.currentUser} />
And in ArticleMeta
, we forward it again, down to ArticleActions
:
<ArticleActions currentUser={props.currentUser} />
Also, the button will need to trigger the HTTP PATCH
request that we implemented in the web API. We add it to the list of article requests:
spam: slug => requests.patch(`/articles/${slug}`)
The button needs to update client-side state too. We add a new action type:
export const SPAM_ARTICLE = 'SPAM_ARTICLE';
And then, in the article reducer, import the action type:
import { SPAM_ARTICLE } from '../constants/actionTypes';
And implement the action:
case SPAM_ARTICLE:
return {
...state,
article: { ...state.article, isSpam: true }
};
Now we’re finally ready to implement the button in the ArticleActions
component by importing the action type:
import { SPAM_ARTICLE } from '../../constants/actionTypes';
And adding a dispatch function to props
:
onClickSpam: payload => dispatch({ type: SPAM_ARTICLE, payload })
And adding a spam
function within the component:
const spam = () => {
props.onClickSpam(agent.Articles.spam(article.slug))
};
And adding the JSX for the button:
if (!article.isSpam && props.currentUser !== null) {
return (
<span>
<button className="btn btn-outline-danger btn-sm" onClick={spam}>
<i className="ion-trash-a"></i> Flag Spam
</button>
</span>
);
}
And now, after ten distinct changes, we have a working button in React.
In EWL
We add the button to the article page, as a page action:
.Concat( !info.Article.IsSpam && AppTools.User != null
? new ButtonSetup( "Flag as Spam", behavior: new PostBackBehavior( postBack: PostBack.CreateFull(
id: "spam" /* not a magic string; just needs to be unique on page */,
firstModificationMethod: () => {
var mod = info.Article.ToModification();
mod.IsSpam = true;
mod.Execute();
} ) ) ).ToCollection()
: Enumerable.Empty<ActionComponentSetup>() )
And that’s it. The button’s action (flagging the article as spam) can only execute when the button is present on the page, so there is no need to have a separate authorization check on the action. Remember that this is server-side code and cannot be bypassed.
This is why abstraction matters
You can now see why we call EWL a “new level of abstraction for enterprise web applications.” If you’re developing a line-of-business application with front-end JavaScript, I hope you have a good reason.
If you like what you just read, subscribe to our mailing list!
Thanks to Don McNamara and Jonathan McKenzie for help with React.