Skip to content

Installing GenericServices

Jon P Smith edited this page Jun 5, 2020 · 11 revisions

This page tells you how to set up GenericServices in your application. The steps are:

  • Install the NuGet library EfCore.GenericServices in the main application so that
    • You can register it with .NET Core dependency injection (DI) service, and...
    • ... you can use ICrudServices in your front-end code.
  • Install the same EfCore.GenericServices library in the project/assembly where your DTOs are, so that you can apply the ILinkToEntity<TEntity> interface to mark the DTOs.
  • If you are using DDD-styled classes with EF Core, then you need to add the NuGet library GenericServices.StatusGeneric if you want to return IStatusGeneric from your DDD methods - see What does GenericServices expect in a DDD-styled entity?.

Registering GenericServices with DI in ASP.NET Core

Once you have installed the EfCore.GenericServices NuGet in your ASP.NET Core application you need to register it in the ConfigureService method in the Startup.cs class.

Using one DbContext

The simplest approach, which assumes you have one application's DbContext, can be done with one extension method called GenericServicesSimpleSetup<TContext>. You can see how I did this my the RazorPageApp here, but I have repeated the code below.

services.GenericServicesSimpleSetup<EfCoreContext>(
   Assembly.GetAssembly(typeof(BookListDto)));

The key parts are:

  1. You must provide the type of your application's DbContext, and that context must have already been registered with DI. GenericServices will register all the entity classes and also register your application's DbContext against the DbContext class.
  2. You need to provide the assemblies that your DTOs are in. I do this by using GetAssembly of one of my DTOs.

Registering for multiple DbContexts

GenericServices can handle multiple DbContexts (known in DDD as bounded contexts). To configure these you need to use a more complex registration arrangement, as shown below

services.ConfigureGenericServicesEntities(typeof(BookDbContext), typeof(OrderDbContext))
    .ScanAssemblesForDtos(Assembly.GetAssembly(typeof(BookListDto)))
    .RegisterGenericServices();

Configuring GenericServices when registering with DI

If you want to provide some configuration then it looks like this

services.GenericServicesSimpleSetup<DevDbContext>(new GenericServicesConfig
    {
        DtoAccessValidateOnSave = true,     //we use  Dto access for Create/Update
        DirectAccessValidateOnSave = true,  //And direct access for Delete
        SaveChangesExceptionHandler = GenericServiceErrorHandler.SaveChangesExceptionHandler
}, Assembly.GetAssembly(typeof(BookListDto)));

See Configuration Options for what all the settings are.

Setting up your DTOs

I describe this in great detail the GenericServices and DTOs page, but basically the DTO needs the empty interface ILinkToEntity<TEntity> added it so that GenericServices can find it, and work out what entity class it is linked to.

The other thing I recommend you do is add a [ReadOnly(true)] attribute to all the properties in the DTO that you DON'T want copied back into the entity class - that is a security feature to stop the wrong properties being updated (DDD-styled entity classes are safe without these, but it helps GenerricServices in finding the right method to call).

Optional CopyErrorsToModelState extension method

There is a NuGet library called EfCore.GenericServices.AspNetCore which contains an extension method called CopyErrorsToModelState, which converts the IStatusGeneric into errors in the ASP.NET Core's ModelState, or for Web APIs.
Please look at the README file for this project for more information.

All the example razor pages on the EfCore.GenericServices repo uses this - here is an example

public void OnGet(int id)
{
    Data = _service.ReadSingle<AddReviewDto>(id);
    //The service will have an error (i.e. _service.IsValid is false) 
    //if there is no entity with that id
    if (!_service.IsValid)
    {
       //This will copy any errors to the ModelState
       //if any of the errors have a property name that matches a Data class's 
       //property name then the method marks the error with that name so that 
       //the error message will appear next to the input field.
       //NOTE: For razor pages you need to prefix the property name with the name 
       //of the PageModel property, which you can do via the optional third param
        _service.CopyErrorsToModelState(ModelState, Data, nameof(Data));
    }
}