EWL versus React/ASP.NET: A code comparison

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; }

(View full diff)

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

(View full diff)

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 */

(View full diff)

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;

(View full diff)

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;
        }
    }
}

(View full diff)

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));
}

(View full diff)

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} />

(View full diff)

And in ArticleMeta, we forward it again, down to ArticleActions:

<ArticleActions currentUser={props.currentUser} />

(View full diff)

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}`)

(View full diff)

The button needs to update client-side state too. We add a new action type:

export const SPAM_ARTICLE = 'SPAM_ARTICLE';

(View full diff)

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 }
  };

(View full diff)

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>
  );
}

(View full diff)

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>() )

(View full diff)

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.