Looking for urgent help with API

The idea behind repositories etc is the separation of dB code to your actual business logic in the service classes. Also you are likely handling a lot more lines of redundant code ie context add etc. Personally haven't used the unit of work pattern but my preference is to always separate the dB stuff out so repos work well.

Also how are you handing unit testing with the amalgamation?

I am actually experimenting with Jason Taylor's Clean Architecture. Whereas he has used a Mediatr pattern I have simply opted for using straight service classes.

It looks like a great pattern but I'm also still leaning toward Repos at this point to completely separate out all db leaks into the service or application logic. I was just seeing how far I can push this and realized that, like @DA-LION-619 says, if you are only ever going to use SQL then great but it still doesn't smell right.

Check his testing out here.

 
HI guys. Could you please give a little feedback on a design route I'm experimenting with.

I ditched repositories and unit of work and went with injecting the dbcontext directly into the service/application layer.

It works great. But I'm not sure if it is a sound way to do things and just needing your feedback please.

The reason this is niggling at me a little is because in the service layer I am still having to call Update and then SaveChanges according to EF requirements. So it feels like there is some coupling/dependency there even if just barely tangible.

Context (Persistence)

C#:
public class ApplicationDbContext : DbContext, IApplicationDbContext
{
    private readonly ICurrentUserService _currentUserService;
    private readonly IDateTimeService _dateTimeService;

    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, IDateTimeService dateTimeService, ICurrentUserService currentUserService) : base(options)
    {
        _dateTimeService = dateTimeService;
        _currentUserService = currentUserService;
    }

    public DbSet<Job> Jobs { get; set; }
 
    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
    {
        foreach (var entry in ChangeTracker.Entries<AuditableEntity>())
        {
            switch (entry.State)
            {
                case EntityState.Added:
                    entry.Entity.CreatedBy = _currentUserService.UserId;
                    entry.Entity.Created = _dateTimeService.Now;
                    break;

                case EntityState.Modified:
                    entry.Entity.LastModifiedBy = _currentUserService.UserId;
                    entry.Entity.LastModified = _dateTimeService.Now;
                    break;
            }
        }

        var result = await base.SaveChangesAsync(cancellationToken);

        return result;
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        foreach (var foreignKey in builder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
        {
            foreignKey.DeleteBehavior = DeleteBehavior.Restrict;
        }
    }
}

Middleware

C#:
services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(
        configuration.GetConnectionString("DefaultConnection"),
        b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName)));

services.AddScoped<IApplicationDbContext>(provider => provider.GetRequiredService<ApplicationDbContext>());

Service/Application

C#:
public class JobService : IJobService
{
    private readonly IApplicationDbContext _context;

    public JobService(IApplicationDbContext context)
    {
        _context = context;
    }

    public async Task<List<Job>> GetAsync()
    {
        return await _context.Jobs
            .Include(x => x.JobType)
            .ToListAsync();
    }

    public async Task<Job> GetByIdAsync(int id)
    {
        return await _context.Jobs
            .Where(x => x.Id == id)
            .Include(x => x.JobType)
            .FirstOrDefaultAsync();
    }

    public async Task<Job> CreateAsync(CreateJobRequest request)
    {
        var entity = new Job()
        {
            JobNo = request.JobNo,
            JobTypeId = request.JobTypeId,
            StartDate = request.StartDate,
            EndDate = request.EndDate,
        };

        _context.Jobs.Add(entity);
        await _context.SaveChangesAsync();

        return entity;
    }

    public async Task<Job> UpdateAsync(UpdateJobRequest request)
    {
        var entity = await _context.Jobs.FindAsync(request.Id);

        entity.Id = request.Id;
        entity.JobNo = request.JobNo;
        entity.StartDate = request.StartDate;
        entity.EndDate = request.EndDate;
        entity.JobTypeId = request.JobTypeId;

        _context.Jobs.Update(entity);
        await _context.SaveChangesAsync();

        return entity;
    }

    public async Task<Job> DeleteAsync(int id)
    {
        var entity = await _context.Jobs.FindAsync(id);

        _context.Jobs.Remove(entity);
        await _context.SaveChangesAsync();

        return entity;
    }
}
This is mostly fine

Repos are a complete waste of time with EF core

I am not saying that abstractions are not important, but I do not believe that wrapping ef core offers any value. There is nothing maintainable in people adding bespoke methods for their specific needs, or modifying existing methods which can then cause unintended side effects for others.

What I don’t like are the services injected into the context (specifically the current user service). The date one is probably fine (I’ve seen these datetime abstractions before and thought it was valid - but cannot recall what they are actually for at this moment :giggle:)
The context lives independently of the “application”, but now your data requires application, and your application requires data
Having a “business entity” base class is fine though. Not sure of the best way to do this nicely though, because you do need DI, so moving this to an extension method won’t work.

“Unit” testing can easily be taken care of by doing exactly what ef core is good at - abstracting the database - and using SQLite in-memory. All your migrations, etc just work. You still need to “seed” your database per test, but you would do this if you mocked anyway, just in a different way.

What you will actually end up with is less “unit” tests and more “integration” tests, which still run basically instantaneously.

I will admit however that I am deeply indoctrinated into the cult of vertical slice architecture
 
Last edited:
I am actually experimenting with Jason Taylor's Clean Architecture. Whereas he has used a Mediatr pattern I have simply opted for using straight service classes.

It looks like a great pattern but I'm also still leaning toward Repos at this point to completely separate out all db leaks into the service or application logic. I was just seeing how far I can push this and realized that, like @DA-LION-619 says, if you are only ever going to use SQL then great but it still doesn't smell right.

Check his testing out here.

Hehe you’re very close vertical slice, you just haven’t hit the pain points.
I’ve seen Jason’s stuff and I like the concepts of ‘guards’ and such but unless you need to be programming defensively against the dark arts (other teams), this approach slows you down as it gets bigger.

You have a JobService, which you would implement anywhere you do something job related I guess.
This is a single point of failure, forget maintainability, if this breaks anything that touches it also breaks.
Also here’s a curve ball because I saw you modelled your entities with Delete Restrict, which service is responsible for that?

If a end user wants to delete a JobType and you tell them to first delete the 100s of jobs, they’ll swear you.
Do you have two delete methods then?
NormalDelete and HardcoreDelete?
See it gets messy very fast as you grow.

In end you’re building features, users are more forgiving when a single feature breaks. Also instead of overriding EF’s methods look at the hook/event listeners it has available + Temporal Tables for auditing .
 
Hehe you’re very close vertical slice, you just haven’t hit the pain points.
I’ve seen Jason’s stuff and I like the concepts of ‘guards’ and such but unless you need to be programming defensively against the dark arts (other teams), this approach slows you down as it gets bigger.

You have a JobService, which you would implement anywhere you do something job related I guess.
This is a single point of failure, forget maintainability, if this breaks anything that touches it also breaks.
Also here’s a curve ball because I saw you modelled your entities with Delete Restrict, which service is responsible for that?

If a end user wants to delete a JobType and you tell them to first delete the 100s of jobs, they’ll swear you.
Do you have two delete methods then?
NormalDelete and HardcoreDelete?
See it gets messy very fast as you grow.

In end you’re building features, users are more forgiving when a single feature breaks. Also instead of overriding EF’s methods look at the hook/event listeners it has available + Temporal Tables for auditing .

That is a lot to think about. I can see how a mediatr pattern might be of some advantage as opposed to a single service point for example.

I have not actually tested deletions of job types to see what happens if I try.

I've decided to go with your earlier statement to rather not let the context handle auditing so I'm going to strip all of that out. It's late now and I'll pick up on this tomorrow with fresh mind.

Thanks for the amazing feedback guys.
 
That is a lot to think about. I can see how a mediatr pattern might be of some advantage as opposed to a single service point for example.

I have not actually tested deletions of job types to see what happens if I try.

I've decided to go with your earlier statement to rather not let the context handle auditing so I'm going to strip all of that out. It's late now and I'll pick up on this tomorrow with fresh mind.

Thanks for the amazing feedback guys.
Well you don’t need to use MediatR, instead of approaching it from a Service POV that does everything, approach it as each View/Page has features behind it.
You’ll find it stops you from wrapping EF as you won’t create a FindById method if you don’t need it.

Also try the deleting in SSMS so you can see the actual execution plan, I know inner, outer join bla bla.
The actual way it joins also matters, loop join, merge join etc. or even join elimination takes place.
Again if you’re only using MSSQL then leveraging things like indexed views which the engine might substitute can give you a perf boost.
 
Last edited:
Hey guys. I've got an issue with sending a registration email on my API side. I am unable to get the origin of the request which I need to compile the registration email link. This is just a basic example I am using to try to get this to work.

Code:
[HttpPost]
public void Post()
{
    var test = Request.Headers["Origin"];
}

It is always empty. I've tried everything I know for around 2 days now. Even toyed with CORS in every which way possible but that field returns blank. I even doctored up a basic console httpclient to consume this endpoint, nada, nothing. The header information says nothing about the originating caller at all.

Test.jpg

What to do?
 
Enable HttpContextAccessor, and inject it into your service

Code:
_httpContextAccessor.HttpContext.Request.Host.Value;

But this isn’t reliable. E.g. if you server is behind a reverse proxy. The value might end up being “localhost” or whatever your proxy/load balancer calls

The best way to handle this is to have a value in appsettings.json that you configure per environment.
 
Enable HttpContextAccessor, and inject it into your service

Code:
_httpContextAccessor.HttpContext.Request.Host.Value;

But this isn’t reliable. E.g. if you server is behind a reverse proxy. The value might end up being “localhost” or whatever your proxy/load balancer calls

The best way to handle this is to have a value in appsettings.json that you configure per environment.

Good call thanks kabal.
 
Good call thanks kabal.
You need to look for x-fowarded or clientIP in the headers if I recall. Forwarded helps as anything that bounced through other servers ( eg a gateway ), should be configured to pass this through, otherwise you are going to get the gateways address instead.
 
You need to look for x-fowarded or clientIP in the headers if I recall. Forwarded helps as anything that bounced through other servers ( eg a gateway ), should be configured to pass this through, otherwise you are going to get the gateways address instead.

I think for now I'll hard code the calling URL into the appsettings.json. It's only ever going to be one site calling the registration, login and other business endpoints anyway. But I will check that out anyway Kosmik, see what I can gleam from that, may come in handy in future.
 
Off topic

I vomited in my mouth reading the words "unit of work" and "repository"

As you were
 
Off topic

I vomited in my mouth reading the words "unit of work" and "repository"

As you were

In all fairness, using the dbcontext as is means dealing with IQueriable and having to rewrite repetitive queries each time you need them, whereas the can be handled in a repository class once. Also using the db context as is means having database concerns bleeding into your services. I can't think of another way to add a clean separation of concerns between business and data concerns without a unit of work and repository pattern. What if you wanted to change your ORM and move away from EF to Dapper. Now you have to alter everything that has a direct dependence on EF.
 
In all fairness, using the dbcontext as is means dealing with IQueriable and having to rewrite repetitive queries each time you need them, whereas the can be handled in a repository class once. Also using the db context as is means having database concerns bleeding into your services. I can't think of another way to add a clean separation of concerns between business and data concerns without a unit of work and repository pattern. What if you wanted to change your ORM and move away from EF to Dapper. Now you have to alter everything that has a direct dependence on EF.
How many engineering hours are wasted between now and then because one day you "might" switch to another ORM?
 
How many engineering hours are wasted between now and then because one day you "might" switch to another ORM?

There have been quite a few changes. Anyone who worked with the older EF remembers how crap they were. Then Dapper had its shining moment, then NHibernate, now we are back on a relatively stable EF core. Besides, having EF Dbsets in your Domain or service layers really doesn't seem to be in keeping with clean separation. Just saying.
 
That example above of Jason Taylor is great, but his ef context is bled through right into his domain and service as an awful dependency.
 
That example above of Jason Taylor is great, but his ef context is bled through right into his domain and service as an awful dependency.
Yes grappling with the same. Imho your domain should not know of EF at all. I tend to use EF for my Commands and Dapper for my Queries. Still hiding Dapper behind repo interfaces. Thus no ORM dependencies leaks into my domain or services.
but i think EF implementation of UOW is poor.
 
It would be awesome if I could take code that is really easy to read/understand and safe to modify, and wrap it in an abstraction that adds no value other than "'leaky abstraction' bad" and "unit tests"
^^^ said no one ever :)

This is apparently "bad".....
C#:
var sshKey = await _applicationContext.SshKeys
    .OrderBy(x => x.Id)
    .FirstOrDefaultAsync(x => x.Fingerprint == fingerprint, cancellationToken);

This is apparently "good"....
C#:
var sshKey = await _sshKeyRepository.GetByFingerprint(fingerprint); //"shared" code that is actually used by one thing, amazing

This is apparently "bad".....
C#:
var users = await _applicationContext.Users
    .Include(x => x.SshKeys) // there was business requirement change that in this feature we also wanted to have the SSH keys for a user, so we just added this `.Include`
    .OrderBy(x => x.CreatedDate)
    .ToListAsync(cancellationToken);

// do some business logic with the users

This is apparently "good".....
C#:
// var users = _userRepository.GetAll(); // We absolutely cannot modify this existing method, as it will "infect" other code that is already using it.
var users = _userRepository.GetAllWithSshKeys(); // more "shared" code. I bet this version of the repo ends up with many "GetAllWithSomeAndSomethingElse" variations. Or we build "senior" code that uses `Expression` and QueryBuilders, and basically build a duplicate, but WORSE version of EF Core.....
// OR
var users = _userRepository.GetAll();
var sshKeys = _sshKeyRepository.GetAllByUsers(users); // wow this is cool
// OR
var users = _userRepository.GetAll();
var sshKeys = users.Select(_sshKeyRepository.GetAllByUser).ToList(); // kill me now please
// OR
// Some variarition of the above.

// do some business logic with the users


This is apparently "bad".....
C#:
// Lets create 2 separate data models in a single feature
var product = new Product {
    Id = Ulid.NewUlid(),
    Name = "awesome product",
};
var something = new Something {
    Id = Ulid.NewUlid(),
    Name = "awesome product",
};

_applicationContext.Products.Add(product);
_applicationContext.Somethings.Add(something);
await _applicationContext.SaveChangesAsync(cancellationToken);

This is apparently "good".....
C#:
// Lets create 2 separate data models in a single feature
var product = new Product {
    Id = Ulid.NewUlid(),
    Name = "awesome product",
};
var something = new Something {
    Id = Ulid.NewUlid(),
    Name = "awesome product",
};

// I have no idea how to sanely do this using "repositories"
_productRepository.Add(product);
_somethingRepository.Add(something);
// how do we save now? Genuinely curious?
_productRepository.Save(); // Do we just do this, and because we "know" the same DB context is injected that both will get saved?
// or do we need to do something more convoluted?
_transactionProvider.StartTransaction(_productRepository, _somethingRepository);
try {
    _productRepository.Save();
   _somethingRepository.Save();
} catch (Exception e) {
    _transactionProvider.Rollback;
} finally {
    _transactionProvider.Commit();
}


I don't think these are contrived examples.
These "maybe" are naive implementations of ORM repositories, but I am not so sure


I use repositories all the time, for useful things

C#:
public interface IFileRepository {}

public class S3FileRepository : IFileRepository {}
public class LocalFileRepository : IFileRepository {}


There have been quite a few changes. Anyone who worked with the older EF remembers how crap they were. Then Dapper had its shining moment, then NHibernate, now we are back on a relatively stable EF core. Besides, having EF Dbsets in your Domain or service layers really doesn't seem to be in keeping with clean separation. Just saying.

Why would you have `DbSet` anywhere in your code besides in the declaration for you DbContext?



I bet all these code bases have "duplicate" "data" and "domain" classes.
e.g. a "Product" that is used in DbContext, and a "Product" that is in the "domain".
They then have a mapper/automapper for each direction.
This is then considered "clean" or "separated", but meanwhile they are just 1:1 hard dependencies, and every time you go in and out of a "layer" you need to convert.
What a complete abomination of clean code.
 
Last edited:
This is apparently "bad".....
C#:
// Lets create 2 separate data models in a single feature
var product = new Product {
Id = Ulid.NewUlid(),
Name = "awesome product",
};
var something = new Something {
Id = Ulid.NewUlid(),
Name = "awesome product",
};

_applicationContext.Products.Add(product);
_applicationContext.Somethings.Add(something);
await _applicationContext.SaveChangesAsync(cancellationToken);

Point taken. Good points actually. I am now using the ApplicationDbContext this way without any UoW or Repositories. My next concern is keep the domain models encapsulated and away from the front end and other classes. Looking at using Mediatr now.

Any thoughts or suggestions welcomed.
 
Hi guys. I have a short but fairly important question. Do you have a standard way or format for your API responses? I know there's a way to hook one up into your Middleware. Or do you just return straight dtos with the usual 200,400 etc?

I wouldn't mind some sort of standard to make it easier for a client to deal with.
 
Hi guys. I have a short but fairly important question. Do you have a standard way or format for your API responses? I know there's a way to hook one up into your Middleware. Or do you just return straight dtos with the usual 200,400 etc?

I wouldn't mind some sort of standard to make it easier for a client to deal with.
I use standard ways in that the responses are predictable.

If you mean - do you do something pointless like wrapping everything in an object that says “success=true, status=200, data=…”, then no, I don’t do that, because it is pointless :)

There already exist specifics on how to return errors.
Purposely fail validation for example in an api controller, and look at the response. Do the same thing with an uncaught exception, or even just return a BadRequest(“something happened”) in your endpoint.

You’ll see all the error response bodies look the same.
 
Top
Sign up to the MyBroadband newsletter
X