1

I want to create a repository base class, every service should inherit from this BaseService.

This is my code:

public class BaseService<TEntity, TAddDto, TUpdateDto, TEntityDto> : IBaseService<TEntity, TAddDto, TUpdateDto, TEntityDto>
        where TEntity : Entity<long>
        where TAddDto : class
        where TUpdateDto : class
        where TEntityDto : class
    {
        private readonly IFreeSql<SqlServerFlag> _fsql;

        public BaseService(IFreeSql<SqlServerFlag> fsql)
        {
            _fsql = fsql;
        }

        public virtual async Task<IResultOutput> AddAsync(TAddDto input)
        {
            var addEntity = input.Adapt<TEntity>();
            await _fsql.Insert(addEntity).ExecuteAffrowsAsync();
            return ResultOutput.Ok();
        }

        public virtual async Task<IResultOutput> UpdateAsync(TUpdateDto input)
        {
            var property = typeof(TUpdateDto).GetProperty("Id");
            if (property == null)
            {
                throw new ArgumentException("Id is not exist");
            }

            var obj = property.GetValue(input);

            await _fsql.Update<TEntity>(input)
                  .Where(LambdaHelper.GetPropertyNameExpression<TEntity>("Id", obj))
                  .ExecuteAffrowsAsync();
            return ResultOutput.Ok();
        }

        public virtual async Task<IResultOutput> GetAsync(long id)
        {
            var dto = await _fsql.Select<TEntity>()
                  .Where(a => a.Id == id)
                  .FirstAsync<TEntityDto>();
            return ResultOutput.Ok();
        }

        public virtual async Task<IResultOutput> FakeDeleteAsync(long id)
        {
            int rows = await _fsql.Update<TEntity>()
                 .Set(LambdaHelper.GetPropertyNameMemberExpression<TEntity>("IsDeleted", true), true)
                 .Where(a => a.Id == id)
                 .ExecuteAffrowsAsync();
            return rows > 0 ? ResultOutput.Ok() : ResultOutput.NotOk();
        }

        public virtual async Task<IResultOutput> DeleteAsync(long id)
        {
            int rows = await _fsql.Delete<TEntity>()
                 .Where(a => a.Id == id)
                 .ExecuteAffrowsAsync();
            return rows > 0 ? ResultOutput.Ok() : ResultOutput.NotOk();
        }
    }
}

This is the IBaseService interface:

public interface IBaseService<TEntity, TAddDto, TUpdateDto, TEntityDto>
       where TEntity : Entity<long>
       where TAddDto : class
       where TUpdateDto : class
       where TEntityDto : class
   {
       Task<IResultOutput> AddAsync(TAddDto input);
       Task<IResultOutput> UpdateAsync(TUpdateDto input);
       Task<IResultOutput> GetAsync(long id);
       Task<IResultOutput> FakeDeleteAsync(long id);
       Task<IResultOutput> DeleteAsync(long id);
   }

I create an InternationalService that inherits from BaseService<,,,> like this:

    public class InternationalService: BaseService<InternationalEntity, InternationalAddInput, InternationalUpdateInput, InternationalDto>, IInternationalService
    {
        public InternationalService(IFreeSql<SqlServerFlag> fsql):base(fsql)
        {
            
        }
    }

The IInternationalService interface looks like this:

public interface IInternationalService: IBaseService<InternationalEntity, InternationalAddInput, InternationalUpdateInput, InternationalDto>
{

}

I inject the service in Program.cs:

// Inject Generic Service
builder.Services.AddScoped(typeof(IBaseService<,,,>), typeof(BaseService<,,,>));
// Inject the end name Service
var types = Assembly.Load("Xin.Service")
                              .GetExportedTypes()
                              .Where(a => a.Name.EndsWith("Service"));

foreach (var type in types)
{
      var interfaces = type.GetInterfaces();

      foreach (var baseType in interfaces)
      {
          builder.Services.AddScoped(baseType, type);
      }
}

I want every service to inherit from BaseService, so I can ignore the basic add update delete...code, but I can't run the code.

Can you please help me? Thanks when I run the app, the app don't work, the error was

 Application terminated unexpectedly
System.ArgumentException: Cannot instantiate implementation type 'Xin.Service.International.IInternationalService' for service type 'Xin.Service.Base.IBaseService`4[Xin.Model.System.InternationalEntity,Xin.Service.International.Dto.InternationalAddInput,Xin.Service.International.Dto.InternationalUpdateInput,Xin.Service.International.Dto.InternationalDto]'.
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.Populate()
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory..ctor(ICollection`1 descriptors)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(ICollection`1 serviceDescriptors, ServiceProviderOptions options)
   at Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection services, ServiceProviderOptions options)
   at Microsoft.Extensions.Hosting.HostApplicationBuilder.Build()
   at Microsoft.AspNetCore.Builder.WebApplicationBuilder.Build()
   at Xin.Admin.WebApi.Program.Main(String[] args) in E:\home\NetCore\code\Xin.Admin\Xin.Admin.WebApi\Program.cs:line 22
5
  • You should put as much effort in your question (research, formatting) as you want from our answers Commented Sep 14 at 3:50
  • 1
    Seems you try to register the interface IInternationalService as implementation type Commented Sep 14 at 3:53
  • Try modifying your filter for types to something like .Where(a => a.Name.EndsWith("Service") && !a.IsInterface && !a.IsAbstract) so it will try to register only the types that are instantiable. Commented Sep 14 at 6:17
  • @GuruStron it is work, thanks Commented Sep 15 at 3:13
  • @ross was glad to help! Adding as an answer. Commented Sep 15 at 8:07

2 Answers 2

1

If I read this correctly you want to implement boilerplate code in a base class, scan for all concrete implementations, and inject them into DI automatically. If so, I think you may be overcomplicating things a little: there doesn't seem a need for interfaces for the concrete implementations such as IInternationalService.

Here's a simple demo to show one way of doing that. Note I've demonstrated using interfaces rather naming conventions to find and register the services.

First a generic interface:

public interface IMyService<T>
    where T : class, new()
{
    public T Data { get;}
    Task<T> GetAsync(long id);
}

And a simple data object:

public record MyData
{
    public string Name { get; init; } = "Not Set";
}

Next the base service definition:

public abstract class BaseService<T>
    where T : class, new()
{
    public Task<T> GetAsync(long id)
        => Task.FromResult<T>(new T() ); 
}

Note no IMyService inheritance: we don't want to capture it in any assembly scanning for the interface.

And finally a concrete implementation:

public class TestService : BaseService<MyData>, IMyService<MyData>
{
    public MyData Data => new MyData() { Name = ($"{this.GetType().FullName}") };
    public TestService() : base() { }
}

The following IServiceCollection extension class scans the main assembly, finds all the IMyService Services and registers each against the interface.

public static class AddServicesExtensions
{
    public static void AddMyServices(this IServiceCollection services)
    {
        var assembly = Assembly.GetCallingAssembly();

        var requestHandlerInterfaceType = typeof(IMyService<>);

        var handlerTypes = assembly
            .GetTypes()
            .Where(type => !type.IsAbstract && !type.IsInterface)
            .SelectMany(type => type.GetInterfaces()
                .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == requestHandlerInterfaceType)
                .Select(i => new { Interface = i, Implementation = type }));

        foreach (var handler in handlerTypes)
        {
            services.AddScoped(handler.Interface, handler.Implementation);
        }
    }
}

And Registration:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();

builder.Services.AddMyServices();

var app = builder.Build();

Here's a Blazor page that uses the service:

@page "/"
@inject IMyService<MyData> _service 
<PageTitle>Home</PageTitle>

<h1>Hello, world! - @_service.Data.Name</h1>

Welcome to your new app.
Sign up to request clarification or add additional context in comments.

Comments

1
  1. The problem comes from the fact that you capture unwanted types to register. Filter out the interfaces (and optionally abstract types):

    var types = Assembly.Load("Xin.Service")
        .GetExportedTypes()
        .Where(a =>
            a.Name.EndsWith("Service")
            && !a.IsInterface
            && !a.IsAbstract);
    
  2. Personally I prefer to avoid dynamically registering services (so you don't bump into corner cases like yours), but without having full context it's hard to tell if approach is valid or not.

    If you decide to go this path I would recommend to look into library like Scrutor which provides assembly scanning and decoration extensions for Microsoft.Extensions.DependencyInjection. Arguably it provides more readable API and does not require to mess with reflection directly.

  3. Arguably Assembly.Load("Xin.Service") is not idiomatic for your case - highly likely that assembly is referenced and is not loaded manually, so you instead of using magic strings - use some type from the assembly (personally I usually create type like XinServiceAssemblyLandmark and use it). Like typeof(SomeServiceInXinService).Assembly. If you will use Scrutor this would be out of the box.

    Another, probably even better, approach would be to just add method like AddXinServices in the Xin.Service project, so you don't need to get the assembly in the composition root (i.e. Program.cs):

    public static class ServiceCollectionExtensions
    {
        public static void AddXinServices(this IServiceCollection services)
        {
            // ... magic here 
        }
    }
    

    And then call it in the Program.cs: builder.Services.AddXinServices();

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.