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.