2

I have multiple controllers and in all of them I need to provide generic types. This causes sort of redundancy as well as issues with type safety

Here are my Controllers

public class DemoController : DemoBaseController<Guid, Guid>
{
    public DemoController(IUnitOfWork<Guid, Guid> uow) : base(uow)
    {
    }
}

public class DemoPermissionController : DemoPermissionBaseController<Guid, Guid>
{
    public DemoPermissionController(IUnitOfWork<Guid, Guid> uow) : base(uow)
    {
    }
}

Program.cs file

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddDemoManagementDI<Guid, Guid, DemoRoleContext>();

In AddDemoManagementDI()

public static void AddDemoManagementDI<TKey, TBKey, TContext>(this IServiceCollection services) where TKey : IEquatable<TKey> where TBKey : IEquatable<TBKey> where TContext : DbContext, IDemoManagementContext<TKey, TBKey>
    {
        IConfiguration configuration = services.BuildServiceProvider().GetService<IConfiguration>();
        services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
        services.AddDbContext<DbContext, TContext>(delegate (DbContextOptionsBuilder options)
        {
            options.UseSqlServer(configuration.GetConnectionString("DefaultConnection"));
        });
        services.AddAuthentication(delegate (AuthenticationOptions options)
        {
            options.DefaultAuthenticateScheme = "Bearer";
            options.DefaultChallengeScheme = "Bearer";
            options.DefaultScheme = "Bearer";
        }).AddJwtBearer(delegate (JwtBearerOptions options)
        {
            options.SaveToken = true;
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateAudience = false,
                ValidateIssuer = false,
                ValidateLifetime = true,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["JWT:Secret"])),
                ClockSkew = TimeSpan.FromMinutes(5.0)
            };
        });
        services.AddAuthorization();
        services.AddCors();
        services.AddSwaggerGen(delegate (SwaggerGenOptions c)
        {);
        services.AddScoped<IRepositoryResponse, RepositoryResponse>();
        services.AddScoped(typeof(IDatabaseGenericRepository<,>), typeof(EntityFrameworkGenericRepository<,>));
        services.AddScoped<IDemoService<TKey, TBKey>, DemoService<TKey, TBKey>>();
        services.AddScoped<IDemoPermissionService<TKey, TBKey>, DemoPermissionService<TKey, TBKey>>();
        services.AddScoped<IUnitOfWork<TKey, TBKey>, UOWService<TKey, TBKey>>();
        services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
        services.AddScoped((IServiceProvider provider) => (IDemoManagementContext<TKey, TBKey>)provider.GetService(typeof(TContext)));
    }

I actually want it to be something like this.

public class RoleController<TUserKey, TAuthKey> : RoleBaseController<TUserKey, TAuthKey>
    {
        public RoleController(IUnitOfWork<TUserKey, TAuthKey> uow) : base(uow)
        {
        }
    }

1 Answers1

0

Build in DI container allows to register open generic types, i.e. the only one following registration:

services.AddScoped(typeof(IUnitOfWork<,>), typeof(UOWService<,>));

will allow next class:

class SomeController<TUserKey, TAuthKey>
{
    private readonly IUnitOfWork<TUserKey, TAuthKey> _uow;

    public SomeController(IUnitOfWork<TUserKey, TAuthKey> uow)
    {
        _uow = uow;
    }
}

Be successfully resolved with all generic type parameters provided:

IServiceProvider sp = app.Services;
using (var scope = sp.CreateScope())
{
    var someController = scope.ServiceProvider.GetRequiredService<SomeController<Guid, Guid>>();
}
Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • Using the above solution, app builds up all fine but the app somehow don't treat them as normal controller. In swagger it says no operations defined in specs! and no endpoint is exposed. – Ammad Khalid Butt Aug 27 '22 at 12:36
  • @AmmadKhalidButt yes, for ASP.NET Core to automatically use your controllers they should be some concrete classes with all generic parameters filled. Note that by default convention controller name is part of the route and for the case of open generic - what ASP should do if you don't provide generic parameters (i.e. should `SomeController` be `SomeController` or `SomeController` or what should happen if you need both). Maybe something can be achieved with custom conventions ([see](l-to-customize-attribute-routes)) but maybe it would be easier not to go there. – Guru Stron Aug 29 '22 at 12:59
  • @AmmadKhalidButt for the controller to be properly recognized by the application and swagger in return you need to derive from `ControllerFeatureProvider` and configure it at Startup as was answered in this [question](https://stackoverflow.com/questions/36680933/discovering-generic-controllers-in-asp-net-core). After that you set `[Route]` and `[ControllerName]` annotations to override the generic name for the Controller. – Tiramonium Apr 24 '23 at 18:16