• Stars
    star
    142
  • Rank 258,495 (Top 6 %)
  • Language
    C#
  • Created about 6 years ago
  • Updated almost 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A RealWorld application implemented using the Dev Adventures .NET Core template and functional programming.

Build Status

RealWorld Example App

A functionally written ASP.NET Core codebase containing real world examples (CRUD, auth, advanced patterns, etc) that adheres to the RealWorld spec and API.

RealWorld Repo

This codebase was created to demonstrate a fully fledged backend application built with ASP.NET Core and Optional. It includes CRUD operations, authentication, routing, pagination, and more.

It completely adheres to the ASP.NET Core community styleguides & best practices.

For more information on how to this works with other frontends/backends, head over to the RealWorld repo.

Features

What's special about this specific implementation is that it employs a different approach on error handling and propagation. It uses the Maybe and Either monads to enable very explicit function declarations and allow us to abstract the conditionals and validations into the type itself.

This allows you to do cool stuff like:

public Task<Option<UserModel, Error>> LoginAsync(CredentialsModel model) =>
    GetUser(u => u.Email == model.Email)
        .FilterAsync<User, Error>(user => UserManager.CheckPasswordAsync(user, model.Password), "Invalid credentials.")
        .MapAsync(async user =>
        {
            var result = Mapper.Map<UserModel>(user);

            result.Token = GenerateToken(user.Id, user.Email);

            return result;
        });

You can read more about Maybe and Either here and here.

Architecture

This application has been made using the Dev Adventures .NET Core template, therefore it follows the architecture of and has all of the features that the template provides.

  • Swagger UI + Fully Documented Controllers

swagger-ui

  • Thin Controllers
/// <summary>
/// Retreives a user's profile by username.
/// </summary>
/// <param name="username">The username to look for.</param>
/// <returns>A user profile or not found.</returns>
/// <response code="200">Returns the user's profile.</response>
/// <response code="404">No user with tha given username was found.</response>
[HttpGet("{username}")]
[ProducesResponseType(typeof(UserProfileModel), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(Error), StatusCodes.Status400BadRequest)]
public async Task<IActionResult> Get(string username) =>
    (await _profilesService.ViewProfileAsync(CurrentUserId.SomeNotNull(), username))
    .Match<IActionResult>(profile => Ok(new { profile }), Error);
  • Robust service layer using the Either monad.
public interface IProfilesService
{
    Task<Option<UserProfileModel, Error>> FollowAsync(string followerId, string userToFollowUsername);

    Task<Option<UserProfileModel, Error>> UnfollowAsync(string followerId, string userToUnfollowUsername);

    Task<Option<UserProfileModel, Error>> ViewProfileAsync(Option<string> viewingUserId, string profileUsername);
}
public Task<Option<UserProfileModel, Error>> FollowAsync(string followerId, string userToFollowUsername) =>
    GetUserByIdOrError(followerId).FlatMapAsync(user =>
    GetUserByNameOrError(userToFollowUsername)
        .FilterAsync(async u => u.Id != followerId, "A user cannot follow himself.")
        .FilterAsync(async u => user.Following.All(fu => fu.FollowingId != u.Id), "You are already following this user")
        .FlatMapAsync(async userToFollow =>
        {
            DbContext.FollowedUsers.Add(new FollowedUser
            {
                FollowerId = followerId,
                FollowingId = userToFollow.Id
            });

            await DbContext.SaveChangesAsync();

            return await ViewProfileAsync(followerId.Some(), userToFollow.UserName);
        }));
  • Safe query string parameter model binding using the Option monad.
public class GetArticlesModel
{
  public Option<string> Tag { get; set; }

  public Option<string> Author { get; set; }

  public Option<string> Favorited { get; set; }

  public int Limit { get; set; } = 20;

  public int Offset { get; set; } = 0;
}
  • AutoMapper
  • EntityFramework Core with SQL Server Postgres and ASP.NET Identity
  • JWT authentication/authorization
  • File logging with Serilog
  • Stylecop
  • Neat folder structure
├───src
│   ├───configuration
│   └───server
│       ├───Conduit.Api
│       ├───Conduit.Business
│       ├───Conduit.Core
│       ├───Conduit.Data
│       └───Conduit.Data.EntityFramework
└───tests
    └───Conduit.Business.Tests
  • Global Model Errors Handler
{
  "messages": [
    "The Email field is not a valid email.",
    "The LastName field is required.",
    "The FirstName field is required."
  ]
}
  • Global Environment-Dependent Exception Handler
// Development
{
    "ClassName": "System.Exception",
    "Message": null,
    "Data": null,
    "InnerException": null,
    "HelpURL": null,
    "StackTraceString": "...",
    "RemoteStackTraceString": null,
    "RemoteStackIndex": 0,
    "ExceptionMethod": null,
    "HResult": -2146233088,
    "Source": "Conduit.Api",
    "WatsonBuckets": null
}

// Production
{
  "messages": [
    "An unexpected internal server error has occurred."
  ]
}
  • Neatly organized solution structure
    solution-structure

Test Suite

  • xUnit
  • Autofixture
  • Moq
  • Shouldly
  • Arrange Act Assert Pattern
[Theory]
[AutoData]
public async Task Login_Should_Return_Exception_When_Credentials_Are_Invalid(CredentialsModel model, User expectedUser)
{
    // Arrange
    AddUserWithEmail(model.Email, expectedUser);

    MockCheckPassword(model.Password, false);

    // Act
    var result = await _usersService.LoginAsync(model);

    // Assert
    result.HasValue.ShouldBeFalse();
    result.MatchNone(error => error.Messages?.Count.ShouldBeGreaterThan(0));
}

Getting started

  1. Set the connection string in src/server/Conduit.Api/appsettings.json to a running Postgres instance. Set the database name to an unexisting one.
  2. Execute dotnet restore
  3. Execute dotnet build
  4. Execute dotnet ef database update
  5. Execute dotnet run

More Repositories

1

practical-haskell

A collection of Practical Haskell bits.
Haskell
84
star
2

cafe

A by the book DDD application with React/Redux and .NET Core. It features CQRS, event-sourcing, functional programming, TDD, Docker and much more.
C#
84
star
3

haskell-tic-tac-toe

A multiplayer web real-time implementation of the famous Tic Tac Toe game in Haskell.
Haskell
60
star
4

devadventures-net-core-template

A Visual Studio extension that provides a robust and maintainable setup for a .NET Core API.
C#
34
star
5

purescriptify

HTML to PureScript converter.
PureScript
29
star
6

solution-snapshotter

Take your existing .NET project and export it as a ready-to-install Visual Studio extension!
F#
28
star
7

servant-purescript-codegen-example

Generate your PureScript types and API client from a Haskell Servant backend.
PureScript
27
star
8

it-has

A Generic implementation of data-has.
Haskell
22
star
9

elasticsearch-recipes-nest-angular

A recipes search engine made using .NET core with Elasticsearch's NEST. It is the finished application from my 4 part tutorial on Elasticsearch on https://devadventures.net
C#
17
star
10

bar-event-sourcing

A sample .NET application featuring DDD, CQRS, event-sourcing, Docker, integration testing and a bit of SignalR.
C#
12
star
11

signalr-integration-tests

C#
7
star
12

optional-async

Async extensions for Nils Luck's Optional library.
C#
5
star
13

haskell-servant-event-sourcing

A simple Haskell web application with event-sourcing.
Haskell
4
star
14

melomania

A CLI tool for managing your music collection. It seamlessly extracts .mp3 tracks from video urls (Youtube, Facebook) and uploads them into the cloud.
C#
3
star
15

onlinestore-net-core-mvc

A sample online store app I made while playing around with .NET core mvc and Jimmy Bogard's MediatR.
C#
2
star
16

notetaker

Helping gtf21 from reddit.
Haskell
1
star
17

lisperati-haskell-picnic

My attempt at following the Lisperati Haskell tutorial. (http://lisperati.com/haskell/)
Haskell
1
star
18

dnikolovv.github.io

JavaScript
1
star
19

haskell-bathroom-scraper

A scraper I made for a friend. Looks up the best online prices for various bathroom goods by product id.
Haskell
1
star