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();
}
}
Related
I have the generic BaseController like this:
public class BaseController<T> : Controller where T : BaseEntity
{
protected readonly IRepository _repository;
public BaseController(IRepository repository)
{
_repository = repository;
}
// POST: TController/Create
[HttpPost]
[ValidateAntiForgeryToken]
public virtual async Task<IActionResult> Create(T item)
{
try
{
if (ModelState.IsValid)
{
await _repository.AddAsync(item);
}
return RedirectToAction(nameof(Index));
}
catch
{
return PartialView();
}
}
Do I correctly override this action in the derived controller class
public class PaysController : BaseController<Pays>
{
public PaysController(IRepository repository): base(repository) { }
// POST: Pays/Create
[HttpPost]
[ValidateAntiForgeryToken]
public override async Task<IActionResult> Create([Bind("IDPays,Code,Nom")] Pays pays)
{
return await base.Create(pays);
}
Especially, should I reuse the method attributes(like ValidateAntiForgeryToken), and will the binding Bind work in that case?
Method attributes do not need to be reused on the overriden method:
var attributes = typeof(PaysController).GetMethod("Create").GetCustomAttributes(false);
Debug.Assert(attributes.Any(x => x.GetType() == typeof(HttpPostAttribute)));
Debug.Assert(attributes.Any(x => x.GetType() == typeof(ValidateAntiForgeryTokenAttribute)));
The binding Bind will work in the overrided method. You will need to mark the base controller as abstract, otherwise ASP.NET Core does not know, which controller and endpoint to choose and throws an exception:
Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The
request matched multiple endpoints
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 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();
}
I have two different controller --> Controller A,Controller B
And I have different methods each controller and their return values IHttpActionResult (Method A controller A ,Method B and Controller B)
How can I access another controller method and take its content from another controller
Controller B ,and Inside Method B
IHttpActionResult result = ControllerA.MethodA()
and I want to read result.content inside controller B
When request comes, only controller which should process request is instantiated automatically. You can instantiate second controller manually, but I would recommend to move MethodA functionality either to base controller class
public class BaseController : ApiController
{
// ...
public IHttpActionResult MethodA(int id)
{
var foo = repository.Get(id);
if (foo == null)
return NotFound();
return Ok(foo);
}
}
public class ControllerA : BaseController
{
//...
}
public class ControllerB : BaseController
{
public IHttpActionResult MethodB(int id)
{
var result = MethodA();
//..
}
}
or move common logic to separate class (e.g. service), so you would be able to call it from both controllers.
public class ControllerA : ApiController
{
private IFooService fooService;
public ControllerA(IFooService fooService)
{
this.fooService = fooService;
}
public IHttpActionResult MethodA(int id)
{
// use fooService.Method()
}
}
public class ControllerB : ApiController
{
private IFooService fooService;
public ControllerB(IFooService fooService)
{
this.fooService = fooService;
}
public IHttpActionResult MethodB(int id)
{
// use fooService.Method()
}
}
I would consider using a common base class for the two controllers (if there is a method you want to use on both)
for example
public abstract class MyBaseController
{
public void CommonMethod()
{
// Do something here
}
}
then use them like
public class ControllerA : MyBaseController
{
public void MethodA()
{
base.CommonMethod();
// Do something else
}
}
public class ControllerB : MyBaseController
{
public void MethodB()
{
base.CommonMethod();
// Do Something else
}
}
1) You can use static class and static method inside to share it for another controllers
public static class CommonMethods
{
public static string SomeMethod(string s)
{
string RetString;
...
return (RetString);
}
}
Now you can use it in any controllers
string SomeMethodResult = CommonMethods.SomeMethod("Say Hello");
2) And another method is to create an instance of a controller class and call instances methods:
public class V1Controller : ApiController
{
public void Put(int id, [FromBody]string value)
{
HomeController hc = new HomeController();
hc.SomeMethod();
}
}
AController aController = new AController();
var getResponse = aController.YourMethod(values);
If your method returns Json then you can easily solve it with
JavaScriptSerializer().Deserialize