DEV Community

Sebastian Larrieu
Sebastian Larrieu

Posted on

ASP.NET Core, Carter and Angular, a scalable RESTfull example for ¿beginners? (1)

Hi everyone, this is my first post in DEV (or any community). It was on my pending list, but i used to think that I had nothing to write about. I'm not a native english speaker so you may find some errors in redaction, be welcome to correct me.

Let me introduce myself, I'm a software engineering from Argentina, two years ago I get my degree and get a job as a front-end developer but because of the company needs i became what i like to call middle-end developer after few months. Why middle-end? because I really enjoy getting dirty with complex problems and business logic but i'm not a big fan of databases and although I like, I'm neither UX nor UI expert.

In my job we focus on building complex solutions with Angular and ASP.NET Core. Why this technologies? because both have types and IoC out the box, two things we consider essential for building large and maintainable code.

Wait, what is CAInD?, it's a terrible name (came on, I'm a computer person) for what I think its a great stack (maybe if anyone read this, could propose a better name in comments): Carter, Angular, Inversion of Control and ASP.NET Core. I've got many good things to say about this technologies, but i don't want to make this post excessively large so I will write abut them in next days.

In this series of tutorials I will write a very basic but over engineered to-cook app (yes, like a to-do but with food). I will start with basics back-end and front-end structures and then refactor to make them ready to scale in to large projects. The original idea was to start front and back project in same post, but it will be too large so lets start with backend.

After some projects we find what we consider a good basic structure for backend projects.

  1. DTOs to move data between frontend and backend.
  2. Rich models (as in DDD) responsable for simple business data validation, I.E properties that can't be null, and complex operations that don't requiere injected dependencies.
  3. Services for operations that requiere injected dependencies, like accessing database to make a validation, or are used in many modules, I.E sending a push notification.
  4. Extension methods for reusable functions, I.E we have IncludeAllDay to ensure that the time is 23:59:49 in a DateTime var.

This topics will be cover in the tutorials serie. Stop the talking and lets write some code.

First step is install .NET Core SDK and create the project.

dotnet new web -n to-cook

then we move to solution folder and install Carter. Carter its a great tool for, quoting his creator, 'allow a more elegant routing'. In my opinion, the best about it it's that you write method inside module's constructor and have access for injected dependencies in them.

cd to-cook
dotnet add package carter

Next we need to add Carter, in startup.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Carter;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using to_cook.Services;

namespace to_cook
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCarter();
        }

        public void Configure(IApplicationBuilder app)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseCarter();
        }
    }
}

We will start with recipes that we would like to cook. Create a Models folder with Recipe.cs. Properties are virtual (and recipe has an id) because later (in another post) we will add Entity Framework.

namespace to_cook.Models
{
    public class Recipe
    {
        public virtual int Id { get; set; }
        public virtual string Title { get; set; }
        public virtual string Description { get; set; }
    }
}

Now we will create a super basic "database" for this tutorial. Later we will change this for real data provider and database connection.
Create a Services folder with a RecipeProviderDB.cs.

using System.Collections.Generic;
using to_cook.Models;

namespace to_cook.Services
{
    public interface IRecipeProviderDB
    {
        ICollection<Recipe> Recipes();
    }

    public class RecipeProviderDB : IRecipeProviderDB
    {
        ICollection<Recipe> _recipes;

        public RecipeProviderDB()
        {
            _recipes = new HashSet<Recipe>()
            {
                new Recipe() { Id = 1, Title = "Hamburger pizza" },
                new Recipe() { Id = 2, Title = "Argentine Asado" },
                new Recipe() { Id = 3, Title = "Mexican Tacos" },
            };
        }

        public ICollection<Recipe> Recipes()
        {
            return this._recipes;
        }
    }
}

Why is an IRecipeProviderDB in my file? well, as I said, one of the things we like most about ASP.NET core is that it provide support for dependency injection pattern out of the box.

You can google more about dependency injection but it's a pattern where you use high level abstractions for yours dependencies so you don't have to worry about implementation details, IoC decouple dependencies making software easier to extend and test.

ASP.NET core requiere 3 steps for its DI pattern:

  1. Define an interface (a contract).
  2. Write a class that implements that interface.
  3. Declare the connection between them. It will become clear in few moments, I promise.

For now you can see that IServiceProviderDB (step 1) has a method that return a collection of Recipe, so anyone who has an IRecipeProviderDB know that can ask him for a Recipe collection.
In the same file we wrote a ServiceProvicerDB class with _recipes, our super simple database, it also implements IserviceProviderDB (step 2) so we have to respect the contract (Recipes() method).

Why put both in same file?, just because it's easy when you use go to definition, but you could put interface in a separate file.

Disclaimer: this is not a real "provider". The main purpose of this tutorial is write the basic structure for a RESTfull API. Be patient.

The next step is to create a RecipeProvider in Services. Again, first we define the contract (step 1).

public interface IRecipeProvider
{
    IEnumerable<Recipe> GetAll();
    Recipe GetById(int id);
    Recipe Add(Recipe recipe);
    Recipe Update(int id, Recipe recipe);
    void Delete(int id);
}

Then we write the actual class (step 2), I will explain the class part by part.

public class RecipeProvider : IRecipeProvider
{
    private IRecipeProviderDB _recipeProviderDB;

    public RecipeProvider(IRecipeProviderDB recipeProviderDB)
    {
            _recipeProviderDB = recipeProviderDB;
    }
}

We have _recipeProvider to store the database that we receive via constructor as a dependence. Finally some concrete DI, as I mentioned early, in this particular point RecipeProvider needs an IRecipeProviderDB so it can ask him for the collection of Recipe but it don't care how, when, or why (specially how) will IRecipeProviderDB get it.

Now that we have the database, and because we are good devs, we will write code to honor the IRecipeProvider contract. Lets go one by one:

GetAll() is very simple, we just return the Recipes.

public IEnumerable<Recipe> GetAll()
{
    return this._recipeProviderDB.Recipes();
}

VERY IMPORTANT In a real world, under no circumstance you will return the actual database but a copy. Remember, the main purpose of this tutorial is describe the basic structure of the project. Also you will see that thanks to DI, the change will be transparent for services consumers.

GetById() is also very basic, we first get all recipes and then find the one with the specified id using LINQ, another super awesome c# tool, FirstOrDefault() return the first element matching the where criteria or null.

public Recipe GetById(int id)
{
    return this._recipeProviderDB.Recipes().Where(x => x.Id == id).FirstOrDefault();
}

Okey, lets go now with next in contract list Add(Recipe recipe), add a new Recipe to list and return it. The code is also very simple.

public Recipe Add(Recipe recipe)
{
    this._recipeProviderDB.Recipes().Add(recipe);
    return recipe;
}

Here come the most difficult method: Update(int id, Recipe recipe) we have to put new data in the recipe with the indicated id. In order to do that we have to check that the id is a valid id. Write code twice is a bad practice (most of times) so we reuse the GetById() method that we already have, remember that it could return null.

public Recipe Update(int id, Recipe recipe)
{
    var oldRecipe = this.GetById(id);
    if (oldRecipe != null)
    {
        oldRecipe.Title = recipe.Title;
        oldRecipe.Description = recipe.Description;
        return oldRecipe;
    }
    else
    {
        return null;
    }
}

Passing properties one by one is tedious, in next posts we will use another complex but great tool called AutoMapper.

We are close to complete the contract, last we have Delete(int id), it's similar to update()

public void Delete(int id)
{
    var recipe = this.GetById(id);
    if (recipe != null)
    {
       this._recipeProviderDB.Recipes().Remove(recipe);
    }
}

Now that we already create all we need to manipulate our recipes, we will add our first Carter module. Create a Modules folder with RecipeModule.cs in it. Carter modules are just c# class that extend CarterModule.

We need import some Carter utilities and tell CarterModule that all methods in this endpoint module will work under /recipe URL.

As you can see, we have one injected IRecipeProvider, that means a contract that said we have access to five method, at this point, we don't know how are they implemented (and we don't care).

Note that we don't need to store the provider in a variable because in Carter we write HTTP methods inside constructor so we can access injected dependencies directly. Carter allow many implementations of HTTP methods, we will use one that receive a string and a delegate that return a Task (delegates are like type-safe pointer to functions or anonymous methods).

using System.Threading.Tasks;
using Carter;
using Carter.ModelBinding;
using Carter.Request;
using Carter.Response;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using to_cook.Models;
using to_cook.Services;

namespace to_cook.Modules
{
    public class RecipeModule : CarterModule
    {
        public RecipeModule(IRecipeProvider recipeProvider) : base("recipe")
        { 
            // Here we will write REST methods
        }
    }
}

RESTful APIs have five basic methods (as we write in our provider)

  • GET / to get all elements.
  • GET /{id} to get one element.
  • POST / to add one element .
  • PUT /{id} to update one element.
  • DELETE /{id} to remove one element.

We will add them where comment is. Lets begin with the first one, we use the provider to get all elements and then let Carter magically transform data in a response Json.

Get("/", ctx =>
{
    var recipes = recipeProvider.GetAll();
    return ctx.Response.AsJson(recipes);
});

This one is a little bit trickier.
First we get the id from the URL with another Carter utility, then we use the provider to search the Recipe, if you remember, GetById will give us a null value if it couldn't find an element, in that case we will return a 404.

Get("/{id:int}", ctx =>
{
    var id = ctx.GetRouteData().As<int>("id");

    var recipe = recipeProvider.GetById(id);

    if (recipe != null)
    {
        return ctx.Response.AsJson(recipe);
    }
    else
    {
        ctx.Response.StatusCode = 404;
        return ctx.Response.WriteAsync($"No recipe with id {id} was found");
    }
});
  • Note 1: WriteAsync will not return a Json, that's not nice, but we will fix that in next tutorials.
  • Note 2: Although WriteAsync is an asynchronous method, if we don't await for it (and declare our method as async), ASP.NET we will treat it as synchronous. That it's not a problem as we are not doing computationally complex functions, if we where, making our method async would be recommended.

Now lets add a Recipe to our list. First we use more Carter magic to convert the POST body in a class, after that we use the provider to save the recipe in the list. Again, this is a first step and we are assuming that all data is correct and will work without problems (real life is harder, specially if you have to deal with user's inputs), latter we will add DTOs for data transfer and some input validation.

Post("/", ctx =>
{
    var recipe = ctx.Request.Bind<Recipe>();

    var addedRecipe = recipeProvider.Add(recipe);

    return ctx.Response.AsJson(addedRecipe);
});

Next method is a http PUT to update an existing Recipe. We made some basic validation as updating an id is (usually) a very very bad idea.
We have a provider to do all the work so we just call him and return a response.

Put("/{id:int}", ctx =>
{
    var id = ctx.GetRouteData().As<int>("id");
    var newRecipe = ctx.Request.Bind<Recipe>();

    if (id != newRecipe.Id)
    {
        ctx.Response.StatusCode = 400;
        return ctx.Response.WriteAsync("Cant update Id property");
    }

    var editedRecipe = recipeProvider.Update(id, newRecipe);
    return ctx.Response.AsJson(editedRecipe);
});

Finally, we have the DELETE, this one is easy. We tell the provider to delete the Recipe but we don't want to return any information so we use a 204 http code (no content) and return a CompletedTask because, if you remember, Carter expect an anonymous method that return a Task.

Delete("/{id:int}", ctx =>
{
    var id = ctx.GetRouteData().As<int>("id");
    recipeProvider.Delete(id);
    ctx.Response.StatusCode = 204;
    return Task.CompletedTask;
});

Congrats, we almost got it. If you are still here you maybe remember that I told you that ASP.NET Core requiere 3 steps to make DI work, we never did step 3 (connect contract with implementation) and, you know, there's no time like the present..

There are 3 possible ways of making that connection

  1. Add singleton: Create an instances in app startup and share the same instance wherever is injected in all http request.
  2. Add scoped: Create a new instance for every http request (share instance in the request lifetime wherever is injected) .
  3. Add transient: Create a new instance wherever is injected.

Scoped lifetime is the common choice for RESTFull APIs as, by definition, they are stateless. We don't want to share information between request.
Our database is a special case beacause we want it to survive between request, so it has to be a singleton.

In Startup.cs go to ConfigureServices and add the next two lines before services.AddCarter():

services.AddSingleton<IRecipeProviderDB, RecipeProviderDB>();
services.AddScoped<IRecipeProvider, RecipeProvider>();

Now you can finally test the solution. Just run:

dotnet build
dotnet run

Open browser and go to localhost:5000/recipe or use the great vscode plugin REST client (there is a .http in my repo with all method example) and TADA:

[{
    "id": 1,
    "title": "Hamburger pizza"
}, {
    "id": 2,
    "title": "Argentine Asado"
}, {
    "id": 3,
    "title": "Mexican Tacos"
}]

We could stop here, but we have to make one more little thing to let things ready for the integration with our future Angular frontend. The necessary buy horrible CORS. We are going to define a custom CORS policy...
Go to Startup.cs and put a

readonly string AllowSpecificOrigins = "_allowSpecificOrigins";

Then in ConfigureServices() we will add two origins, I will put default Angular port but you could write any URL you like.

services.AddCors(options =>
    {
        options.AddPolicy(AllowSpecificOrigins,
            builder =>
            {
                builder.WithOrigins("http://localhost:4200", "https://localhost:4200")
                    .AllowAnyHeader()
                    .AllowAnyMethod();
             });
    });

Last but not least, we have to tell ASP.NET to use the policy, so in configure() we add:

 app.UseCors(AllowSpecificOrigins);

Now we are done.
This is a screenshot of the project structure:

And the Github repo.

Now we can continue with frontend

Discussion (1)

Collapse
wassimchegham profile image
Wassim Chegham

Welcome Sebastian. Thank you for this detailed post 👍