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();
}
Related
I'm writing an API for my game and I'm starting to realize that the amount of GET, POST, and PUT API methods can really add up.
So right now, I'm trying to make it more generic so that I don't have to write a separate method like GetMonsterList, GetTreasureList, GetPlayerInfo, etc.
But I'm not quite sure how to go about doing that.
Here is a non-generic PUT method that I currently have.
// PUT: api/MonsterLists/5
[HttpPut("{id}")]
public async Task<IActionResult> PutMonsterList(string id, MonsterList monsterList)
{
if (id != monsterList.MonsterId)
{
return BadRequest();
}
_context.Entry(monsterList).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MonsterListExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
And here is my attempt at outlining a generic method:
// PUT: api/AnyLists/5
[HttpPut("{id}")]
public async Task<IActionResult> PutAnyList(string id, AnyList anyList)
{
if (id != anyList.AnyId)
{
return BadRequest();
}
_context.Entry(anyList).State = EntityState.Modified;
return NoContent();
}
My problem that I don't understand is, how do I pass in a model to a generic control like this? Like if I have a model for MonsterList, TreasureList, PlayerInfo, WeaponList, etc.
How could I use one generic method for all of them?
I did find one similiar question here, Generic Web Api controller to support any model , but the answer seemed to imply that this isn't a good idea.
Is that possible?
Thanks!
Before we create the generic controller, it is worth to mention that the structure model of your entities is so important to easily or hardly build the generic controller.
For example you could have some models with int id and others with string id, so we need to have a common base for both types.
Start by creating the common interface for Id property to handle int or string Ids in the generic interface:
public interface IHasId<TKey>
where TKey : IEquatable<TKey>
{
TKey Id { get; set; }
}
Another thing to consider is ordering the entities, when querying for a list of entities we need to sort them to get the right paged entities. So, we can create another interface to specify the sorting property e.g. Name.
public interface IOrdered
{
string Name { get; set; }
}
Our objects must implement the common interfaces like below:
public class Player : IHasId<string>, IOrdered
{
public string Id { get; set; }
public string Name { get; set; }
...
}
public class Treasure : IHasId<int>, IOrdered
{
public int Id { get; set; }
public string Name { get; set; }
...
}
Now create a generic base api controller, make sure to mark the methods as virtual so we can override them in the inherited api controllers if necessary.
[Route("api/[controller]")]
[ApiController]
public class GenericBaseController<T, TKey> : ControllerBase
where T : class, IHasId<TKey>, IOrdered
where TKey : IEquatable<TKey>
{
private readonly ApplicationDbContext _context;
public GenericBaseController(ApplicationDbContext context)
{
_context = context;
}
// make methods as virtual,
// so they can be overridden in inherited api controllers
[HttpGet("{id}")]
public virtual T Get(TKey id)
{
return _context.Set<T>().Find(id);
}
[HttpPost]
public virtual bool Post([FromBody] T value)
{
_context.Set<T>().Add(value);
return _context.SaveChanges() > 0;
}
[HttpPut("{id}")]
public virtual bool Put(TKey id)
{
var entity = _context.Set<T>().AsNoTracking().SingleOrDefault(x => x.Id.Equals(id));
if (entity != null)
{
_context.Entry<T>(value).State = EntityState.Modified;
return _context.SaveChanges() > 0;
}
return false;
}
[HttpDelete("{id}")]
public virtual bool Delete(TKey id)
{
var entity = _context.Set<T>().Find(id);
if (entity != null)
{
_context.Entry<T>(entity).State = EntityState.Deleted;
return _context.SaveChanges() > 0;
}
return false;
}
[HttpGet("list/{pageNo}-{pageSize}")]
public virtual (IEnumerable<T>, int) Get(int pageNo, int pageSize)
{
var query = _context.Set<T>();
var totalRecords = query.Count();
var items = query.OrderBy(x => x.Name)
.Skip((pageNo - 1) * pageSize)
.Take(pageSize)
.AsEnumerable();
return (items, totalRecords);
}
}
The rest is easy, just create api controllers that inherits from the base generic controller:
PlayersController :
[Route("api/[controller]")]
[ApiController]
public class PlayersController : GenericBaseController<Player, string>
{
public PlayersController(ApplicationDbContext context) : base(context)
{
}
}
TreasuresController :
[Route("api/[controller]")]
[ApiController]
public class TreasuresController : GenericBaseController<Treasure, int>
{
public TreasuresController(ApplicationDbContext context) : base(context)
{
}
}
you don't have to create any methods, but you are still able to override the base methods since we marked them as virtual e.g.:
[Route("api/[controller]")]
[ApiController]
public class TreasuresController : GenericBaseController<Treasure, int>
{
public TreasuresController(ApplicationDbContext context) : base(context)
{
public ovedrride Treasure Get(int id)
{
// custom logic ….
return base.Get(id);
}
}
}
You can download a sample project from GitHub: https://github.com/LazZiya/GenericApiSample
I guess you can pass over the name of the type of the parameter and do something like this (not tested):
// PUT: api/AnyLists/5
[HttpPut("{id}")]
public async Task<IActionResult> PutAnyList(string id, object anyList, string anyListType)
{
var anyListObject = Convert.ChangeType(anyList, Type.GetType(anyListType)));
if (id != anyListObject.AnyId)
{
return BadRequest();
}
_context.Entry(anyListObject).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
// Whatever error handling you need
}
return NoContent();
}
However, I wouldn't recommend to use this in production code. What will likely happen is that you will need to create quite a lot of exceptions for different types in the end - and you'll end up with the code that is much more convoluted and hard to support than if you just had separate methods per type.
Also, I'm not sure it will be easy to test this.
So, I have for example this Laravel Resource Controller code like this:
class BaseAPIController extends Controller
{
public function index()
{
return self::$model->all();
}
}
So, I was trying to do like that in ASP.NET C#:
[ApiController]
public class BaseAPIController<T> : ControllerBase
{
[HttpGet]
public ActionResult<IEnumerable<T>> Get()
{
using (ExamRTContext db = new ExamRTContext())
{
return db.${typeof(T).Name}.Select(x => x);
}
}
}
But I don't have any idea how to do like that.
So, Let say I just wanted to do simple CRUD in 3 tables. All operation is same, for example Get() is used to get all data from that model.
Instead of writing it 3 times, I wanted to just write it once and extend it to each model controller.
Any idea how to do that?
C# does not allow you to compose expressions at runtime like that.
However, EF has an API to do this.
You're looking for .Set<T>().
If you want to perform simple CRUD operations with entity framework you could create a generic repository.
Repository:
public class GenericRepository<TEntity, TContext>
where TContext : DbContext
where TEntity : class
{
protected readonly TContext context;
public GenericRepository(TContext context)
{
this.context = context;
}
public virtual async Task Add(TEntity model)
{
await context.Set<TEntity>().AddAsync(model);
await context.SaveChangesAsync();
}
public virtual async Task<TEntity> Get(int id)
{
return await context.Set<TEntity>().FindAsync(id);
}
public virtual async Task<IEnumerable<TEntity>> GetAll()
{
return await context.Set<TEntity>().ToListAsync();
}
public virtual async Task<TEntity> FindFirstBy(Func<TEntity,bool> predicate)
{
return await Task.Run(()=> context.Set<TEntity>().FirstOrDefault(predicate));
}
public virtual async Task<IEnumerable<TEntity>> FilterBy(Func<TEntity,bool> predicate)
{
return await Task.Run(()=> context.Set<TEntity>().Where(predicate).ToList());
}
public virtual async Task Update()
{
await context.SaveChangesAsync();
}
public virtual async Task Remove(TEntity model)
{
context.Set<TEntity>().Remove(model);
await context.SaveChangesAsync();
}
}
To be able to use it you just have to inject it in the controller specifying the Entity Type and the Context. In your example it would be like:
Controller Base:
[ApiController]
public class BaseAPIController<T> : ControllerBase
{
protected readonly GenericReposoitory<T,ExamRTContext> repository;
public BaseAPIController(GenericRepository<T,ExamRTContext> repository) {
this.repository = repository;
}
[HttpGet]
public ActionResult<IEnumerable<T>> Get()
{
var entities = repository.GetAll();
if (entities!= null) {
return Ok(entities);
}
return NotFound();
}
}
In Startup:
services.AddTransient(typeof(GenericRepository<,>), typeof(GenericRepository<,>));
I have a simple web-app with angular on client-side and asp.net core web-api on server-side. I use InMemoryDatabase
services.AddDbContext<ItemsContext>(options => options.UseInMemoryDatabase("ItemsDB"));
to store data for the simplisity of the development. But I've encountered an issue with that. I have one controller on web-api to response for users' requests:
[Route("api/[controller]")]
public class ItemsController : Controller
{
private readonly IApiService apiService;
public ItemsController(IApiService apiService)//using DI from Startup.cs
{
this.apiService = apiService;
}
[HttpPost, Route("addItem")]
public async Task<Response> Add([FromBody]Item item)
{
return await apiService.Add(item);
}
[HttpDelete("{id}")]
public async Task<Response> Delete(int id)
{
return await apiService.Delete(id);
}
[HttpPut]
public async Task<Response> Put([FromBody]Item item)
{
return await apiService.Put(item);
}
}
and the following Startup.cs configurations:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDbContext<ItemsContext>(options => options.UseInMemoryDatabase("ItemsDB"));
services.AddSingleton<IUnitOfWork, UnitOfWork>(provider => {
var context = services.BuildServiceProvider().GetService<ItemsContext>();
return new UnitOfWork(context);
});
services.AddSingleton<IApiService, ApiService>(provider => {
return new ApiService(services);
});
}
The problem is, that when I add new item, everything goes just fine...but then I post another request to delete this item it may show there there is no such an item at all or sometimes it may delete it...so in other words, the database exists and then disappears and I'm not sure when. Here is some additional code refering to the above
public class ApiService: IApiService
{
private readonly IUnitOfWork database;
private readonly IServiceProvider provider;
public ApiService(IServiceCollection serviceCollection)
{
provider = serviceCollection.BuildServiceProvider();
}
public IUnitOfWork Database
{
get
{
return provider.GetService<IUnitOfWork>();
}
}
public async Task<Response> Add(Item item)
{
Database.Items.Add(item);
await Database.SaveAsync();
var id = Database.Items.LastItem().Id;
return new Response() { Result = true, ItemId = id };
}
public async Task<Response> Delete(int id)
{
var item = await db.Items.Find(id);
Database.Items.Remove(item);
await Database.SaveAsync();
return new Response() { Result = true };
}
public async Task<Response> Put(Item item)
{
Database.Items.Update(item);
await Database.SaveAsync();
return new Response() { Result = true };
}
}
Update:
UnitOfWork Implementation:
public class UnitOfWork: IUnitOfWork
{
private readonly DbContext context;
private IRepository<Item> itemsRepository;
public UnitOfWork(DbContext dbContext)
{
context = dbContext;
}
public IRepository<Item> Items
{
get
{
return itemsRepository ?? (itemsRepository = new Repository<Item>(context));
}
}
public void Dispose()
{
context.Dispose();
}
public void Save()
{
context.SaveChanges();
}
public async Task SaveAsync()
{
await context.SaveChangesAsync();
}
}
Your code has multiple serious problems, let's go through them.
services.AddDbContext adds a Scoped service, meaning that instances will be created and disposed on each request. services.AddSingleton adds a Singleton service, so only a single instance will ever be created. You cannot add a scoped service to a singleton one, because the reference the singleton service uses will be disposed and you will end up with a disposed context.
This code:
return provider.GetService<IUnitOfWork>();
represents the service locator anti-pattern. As you can guess, an anti-pattern is something you want to avoid. I also don't know why you would want a service to build the entire DI container nor why you would want a service to have the responsibility of getting the dependencies it needs itself.
This part here is where your question actually comes from:
Database.SaveAsync();
You are calling an asynchronous function and not awaiting for it to finish. The task may finish or not, it may throw an error or not, you will never know what happened.
The best thing is that all of these could be avoided if people stopped attempting to create a Unit of Work + Repository pattern over yet another Unit of Work and Repository. Entity Framework Core already implements these:
DbContext => Unit of Work
DbSet => Repository (generic)
Why do you want yet another abstraction? Will you really ever throw away EF Core from the project to justify the maintenance cost of your code?
The entire question code could have just been this:
[Route("api/[controller]")]
public class ItemsController : Controller
{
private readonly YourContext _context;
public ItemsController(YourContext context)
{
_context = context;
}
[HttpPost]
public async Task<IActionResult> Add([FromBody]Item item)
{
context.Items.Add(item);
await context.SaveChangesAsync();
return Ok(item.Id);
}
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
var item = await context.Items.FindAsync(id);
context.Items.Remove(item);
await context.SaveChangesAsync();
return Ok();
}
[HttpPut]
public async Task<IActionResult> Put([FromBody]Item item)
{
context.Items.Update(item);
await context.SaveChangesAsync();
return Ok();
}
}
I got two console applications which calls my webapi the same time and I get back in the console application the follow response from my api:
A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.
So they call at the same time my webapi and then something inside the webapi cannot handle those 2 async calls so this error is returned.
I checked all my code on the webapi project and all methods are async and got await so I cannot see why I get this.
Here is the code of the webapi.
Controller:
public class FederationsController : ApiController
{
private readonly IFederationRepository _federationRepository;
public FederationsController(IFederationRepository federationRepository)
{
_federationRepository = federationRepository;
}
[HttpGet]
[Route("federations", Name = "GetFederations")]
public async Task<IHttpActionResult> GetFederations()
{
var federations = await _federationRepository.GetAllAsync();
return Ok(federations.ToModel());
}
}
Repository
public class FederationRepository : IFederationRepository, IDisposable
{
private Models.DataAccessLayer.CompetitionContext _db = new CompetitionContext();
#region IQueryable
private IQueryable<Models.Entities.Federation> FederationWithEntities()
{
return _db.Federations.Include(x => x.Clubs)
.Where(x => !x.DeletedAt.HasValue && x.Clubs.Any(y => !y.DeletedAt.HasValue));
}
#endregion IQueryable
public async Task<IEnumerable<Models.Entities.Federation>> GetAllAsync()
{
return await FederationWithEntities().ToListAsync();
}
}
Mapper
public static class FederationMapper
{
public static List<Federation> ToModel(this IEnumerable<Models.Entities.Federation> federations)
{
if (federations == null) return new List<Federation>();
return federations.Select(federation => federation.ToModel()).ToList();
}
public static Federation ToModel(this Models.Entities.Federation federation)
{
return new Federation()
{
Name = federation.Name,
FederationCode = federation.FederationCode,
CreatedAt = federation.CreatedAt,
UpdatedAt = federation.UpdatedAt
};
}
}
DbContext
public class CompetitionContext : DbContext
{
public CompetitionContext() : base("ContextName")
{
}
public DbSet<Federation> Federations { get; set; }
}
UnityConfig
public static class UnityConfig
{
public static void RegisterComponents()
{
var container = new UnityContainer();
container.RegisterType<IFederationRepository, FederationRepository>();
GlobalConfiguration.Configuration.DependencyResolver = new UnityDependencyResolver(container);
}
}
Thank you for all the advices/help.
In your repository you are creating a single CompetitionContext and reusing it. I'm assuming that IoC setup is registring the repository as some kind of single instance, so the same repository is getting used every time. If that's the case you should create a new CompetitionContext for each method call.
Also, probably should make sure it's closed with a using statement.
I'm also not clear from your code snippets why you are returning an IQueryable from that FederationWithEntities, method, do you have other things that are using it?
Anyway, I'd probably change that GetAllMethod to be something like this:
public async Task<IEnumerable<Models.Entities.Federation>> GetAllAsync()
{
using (Models.DataAccessLayer.CompetitionContext _db = new CompetitionContext())
{
return _db.Federations.Include(x => x.Clubs)
.Where(x => !x.DeletedAt.HasValue && x.Clubs.Any(y => !y.DeletedAt.HasValue))
.ToListAsync();
}
}
I am trying to implement a base REST controller in aspnetcore 1.0.1 (kind of inspired from NancyFx) and it feels like this should be something that can be achieved with such a composable framework, however, I just cant get it right. The google foo is clearly weak with me today!
I have the following base controller (obviously not fully implemented yet)...
[Route("api/[controller]")]
public abstract class RestApiController<T> : Controller
{
protected abstract Func<int, Task<T>> Get { get; }
protected abstract Func<Task<IEnumerable<T>>> List { get; }
[HttpGet()]
protected virtual async Task<IEnumerable<T>> OnList()
{
if (this.List == null)
{
this.NotFound();
}
return await this.List.Invoke();
}
[HttpGet("{id:int}")]
protected virtual async Task<T> OnGet(int id)
{
if (this.Get == null)
{
this.NotFound();
}
return await this.Get.Invoke(id);
}
}
Which is inherited by the actual controller doing the work...
public class ArticleSummariesController : RestApiController<ArticleExtension>
{
private readonly ArticleManager articleManager;
protected override Func<int, Task<ArticleExtension>> Get => null;
protected override Func<Task<IEnumerable<ArticleExtension>>> List => this.ListAll;
public ArticleSummariesController(ArticleManager articleManager)
{
this.articleManager = articleManager;
}
private async Task<IEnumerable<ArticleExtension>> ListAll()
{
return await this.articleManager.GetAllAsync();
}
}
The idea is that the base controller will be responsible for handling the actual requests but delegate responsibility to it's children to provide and manipulate the data. This is so that we can ensure REST conformance in the requests but loosely couple domain logic from the controllers into "managers" that act as a facade and take repositories and apply business logic.
The problem with the code so far is that the HttpGet() attributes on the base class do not produce routes for the child class. The controller route attribute on the base class is inherited though (as stated in the docs).
I could be wrong, but I immediately assume that you need a RouteAttribute on either the base class or the subclass. For example:
[Route("api/[controller]")]
public abstract class RestApiController<T> : Controller
{
protected abstract Func<int, Task<T>> Get { get; }
protected abstract Func<Task<IEnumerable<T>>> List { get; }
[HttpGet, Route("list")]
protected virtual async Task<IEnumerable<T>> OnList()
{
if (this.List == null)
{
this.NotFound();
}
return await this.List.Invoke();
}
[HttpGet, Route("get/{id:int}")]
protected virtual async Task<T> OnGet(int id)
{
if (this.Get == null)
{
this.NotFound();
}
return await this.Get.Invoke(id);
}
}
Yet again, the answer was staring me in the face! I had the parent methods declared as protected not public so they were not considered eligible actions!
After reading more about the ApplicationModel here it all became clear...
ActionModel – represents an action of a controller. An instance of
this class is created for each eligible action on a controller. There
are multiple requirements for a method to become an action, such as
being public, non-abstract and not inherited from object.
So the modified code below works...
[Route("api/[controller]")]
public abstract class RestApiController<T> : Controller
{
protected abstract Func<int, Task<T>> Get { get; }
protected abstract Func<Task<IEnumerable<T>>> List { get; }
[HttpGet()]
public virtual async Task<IEnumerable<T>> OnList()
{
if (this.List == null)
{
this.NotFound();
}
return await this.List.Invoke();
}
[HttpGet("{id:int}")]
public virtual async Task<T> OnGet(int id)
{
if (this.Get == null)
{
this.NotFound();
}
return await this.Get.Invoke(id);
}
}
public class ArticleSummariesController : RestApiController<ArticleExtension>
{
private readonly ArticleManager articleManager;
protected override Func<int, Task<ArticleExtension>> Get => null;
protected override Func<Task<IEnumerable<ArticleExtension>>> List => this.ListAll;
public ArticleSummariesController(ArticleManager articleManager)
{
this.articleManager = articleManager;
}
private async Task<IEnumerable<ArticleExtension>> ListAll()
{
return await this.articleManager.GetAllAsync();
}
}