简体   繁体   中英

.NET Core API action with generics and AutoMapper

I am building an ASP.NET Core API. I have an action that I want to be essentially identical across a set of controllers. So, I created the EntityController that those controllers inherit from as below.

Note: The ellipsis used in both classes below represent many more actions and their related services following the same pattern omitted for simplicity.

public class EntityController : BaseController
{
    protected readonly SeedService SeedService;

    protected EntityController(IMemoryCache memoryCache, SeedService seedService) : base(memoryCache)
    {
        SeedService = seedService;
    }

    [HttpGet]
    public async Task<IActionResult> Seed()
    {
        var controllerName = ControllerContext.RouteData.Values["controller"].ToString();
        return await GetSeed(controllerName);
    }

    private async Task<IActionResult> GetSeed(string controllerName)
    {
        switch (controllerName)
        {
            case "lists":
                return await MemoryCache.GetOrCreateAsync(CacheKeys.Entry, async entry =>
                {
                    entry.SlidingExpiration = TimeSpan.FromSeconds(3);
                    return Json(await SeedService.GetAllFilterLists());
                });
            case "languages":
                return await MemoryCache.GetOrCreateAsync(CacheKeys.Entry, async entry =>
                {
                    entry.SlidingExpiration = TimeSpan.FromSeconds(3);
                    return Json(await SeedService.GetAllLanguages());
                });
            ...
            default:
                return await Task.FromResult(NotFound());
        }
    }
}

Here are the service methods that these actions call:

public class SeedService
{
    private readonly FilterListsDbContext filterListsDbContext;

    public SeedService(FilterListsDbContext filterListsDbContext)
    {
        this.filterListsDbContext = filterListsDbContext;
    }

    public async Task<IEnumerable<FilterListSeedDto>> GetAllFilterLists()
    {
        return await filterListsDbContext.Set<FilterList>().ProjectTo<FilterListSeedDto>().ToListAsync();
    }

    public async Task<IEnumerable<LanguageSeedDto>> GetAllLanguages()
    {
        return await filterListsDbContext.Set<Language>().ProjectTo<LanguageSeedDto>().ToListAsync();
    }

    ...
}

How can I use generics (or alternative) to reduce this copy/paste duplication? I tried using something like a Dictionary<string, Type> to lookup the Type dynamically from the controller name, but I am not sure how the resulting GetAll<T>() method in SeedService would look? Below doesn't work because the method depends on the types of both the entity and DTO models for the AutoMapper projection.

public async Task<IEnumerable<T>> GetAll<T>()
{
    return await filterListsDbContext.Set<T>().ProjectTo<T>().ToListAsync();
}

You could easily remove all that boilerplate code into a single generic method:

public async Task<IEnumerable<TResult>> GetAll<TEntry, TResult>() where TEntry : class
{
    return await filterListsDbContext.Set<TEntry>()
        .ProjectTo<TResult>()
        .ToListAsync();
}

Since you are returning an IEnumerable , you may want to change to .ToArrayAsync() . Also, since you are projecting to non-entities, and hence changes won't be picked up by the context, you could go further and add .AsNoTracking() to avoid adding the entities to the context:

public async Task<IEnumerable<TResult>> GetAll<TEntry, TResult>() where TEntry : class
{
    return await filterListsDbContext.Set<TEntry>()
        .AsNoTracking()
        .ProjectTo<TResult>()
        .ToArrayAsync();
}

As I mentioned in the comments, you could put that in a base controller and do something like this:

public class BaseController<TEntity, TViewModel>
{    
    public async Task<IEnumerable<TViewModel>> GetAll()
    {
        return await filterListsDbContext.Set<TEntity>()
            .AsNoTracking()
            .ProjectTo<TViewModel>()
            .ToArrayAsync();
    }
}

public class LanguageController : BaseController<Language, LanguageSeedDto>
{
    (in some action)
    var data = await GetAll();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM