generic methods for API controller - c#

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.

Related

The request matched multiple endpoints but why?

I have a controller that has multiple routes.
I am trying to call an endpoint stated as
GET: api/lookupent/2020-03-17T13:28:37.627691
but this results in this error
Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints. Matches:
Controllers.RecordController.Get (API)
Controllers.RecordController.GetRecordRegisteredAt (API)
but I am not sure I understand why this makes sense since this code
// GET: api/{RecordName}/{id}
[HttpGet("{RecordName}/{id}", Name = "GetRecord")]
public ActionResult Get(string RecordName, long id)
// GET: api/{RecordName}/{timestamp}
[HttpGet("{RecordName}/{timestamp}", Name = "GetRecordRegisteredAt")]
public ActionResult GetRecordRegisteredAt(string RecordName, string timestamp)
why does the input match with these endpoints?
You can fix this using route constraints.
Take a look at https://learn.microsoft.com/en-us/aspnet/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
Here's their example:
[Route("users/{id:int}")]
public User GetUserById(int id) { ... }
[Route("users/{name}")]
public User GetUserByName(string name) { ... }
The problem you have is that your controller has the same routing for 2 different methods receiving different parameters.
Let me illustrate it with a similar example, you can have the 2 methods like this:
Get(string entityName, long id)
Get(string entityname, string timestamp)
So far this is valid, at least C# is not giving you an error because it is an overload of parameters. But with the controller, you have a problem, when aspnet receives the extra parameter it doesn't know where to redirect your request.
You can change the routing which is one solution.
This solution gives you the ability to map your input to a complex type as well, otherwise use Route constraint for simple types
Normally I prefer to keep the same names and wrap the parameters on a DtoClass, IntDto and StringDto for example
public class IntDto
{
public int i { get; set; }
}
public class StringDto
{
public string i { get; set; }
}
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
[HttpGet]
public IActionResult Get(IntDto a)
{
return new JsonResult(a);
}
[HttpGet]
public IActionResult Get(StringDto i)
{
return new JsonResult(i);
}
}
but still, you have the error. In order to bind your input to the specific type on your methods, I create a ModelBinder, for this scenario, it is below(see that I am trying to parse the parameter from the query string but I am using a discriminator header which is used normally for content negotiation between the client and the server(Content negotiation):
public class MyModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
dynamic model = null;
string contentType = bindingContext.HttpContext.Request.Headers.FirstOrDefault(x => x.Key == HeaderNames.Accept).Value;
var val = bindingContext.HttpContext.Request.QueryString.Value.Trim('?').Split('=')[1];
if (contentType == "application/myContentType.json")
{
model = new StringDto{i = val};
}
else model = new IntDto{ i = int.Parse(val)};
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
Then you need to create a ModelBinderProvider (see that if I am receiving trying to bind one of these types, then I use MyModelBinder)
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(IntDto) || context.Metadata.ModelType == typeof(StringDto))
return new MyModelBinder();
return null;
}
and register it into the container
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options =>
{
options.ModelBinderProviders.Insert(0, new MyModelBinderProvider());
});
}
So far you didn't resolve the issue you have but we are close. In order to hit the controller actions now, you need to pass a header type on the request: application/json or application/myContentType.json. But in order to support conditional logic to determine whether or not an associated action method is valid or not to be selected for a given request, you can create your own ActionConstraint. Basically the idea here is to decorate your ActionMethod with this attribute to restrict the user to hit that action if he doesn't pass the correct media type. See below the code and how to use it
[AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = true)]
public class RequestHeaderMatchesMediaTypeAttribute : Attribute, IActionConstraint
{
private readonly string[] _mediaTypes;
private readonly string _requestHeaderToMatch;
public RequestHeaderMatchesMediaTypeAttribute(string requestHeaderToMatch,
string[] mediaTypes)
{
_requestHeaderToMatch = requestHeaderToMatch;
_mediaTypes = mediaTypes;
}
public RequestHeaderMatchesMediaTypeAttribute(string requestHeaderToMatch,
string[] mediaTypes, int order)
{
_requestHeaderToMatch = requestHeaderToMatch;
_mediaTypes = mediaTypes;
Order = order;
}
public int Order { get; set; }
public bool Accept(ActionConstraintContext context)
{
var requestHeaders = context.RouteContext.HttpContext.Request.Headers;
if (!requestHeaders.ContainsKey(_requestHeaderToMatch))
{
return false;
}
// if one of the media types matches, return true
foreach (var mediaType in _mediaTypes)
{
var mediaTypeMatches = string.Equals(requestHeaders[_requestHeaderToMatch].ToString(),
mediaType, StringComparison.OrdinalIgnoreCase);
if (mediaTypeMatches)
{
return true;
}
}
return false;
}
}
Here is your final change:
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
[HttpGet]
[RequestHeaderMatchesMediaTypeAttribute("Accept", new[] { "application/json" })]
public IActionResult Get(IntDto a)
{
return new JsonResult(a);
}
[RequestHeaderMatchesMediaTypeAttribute("Accept", new[] { "application/myContentType.json" })]
[HttpGet]
public IActionResult Get(StringDto i)
{
return new JsonResult(i);
}
}
Now the error is gone if you run your app. But how you pass the parameters?:
This one is going to hit this method:
public IActionResult Get(StringDto i)
{
return new JsonResult(i);
}
And this one the other one:
public IActionResult Get(IntDto a)
{
return new JsonResult(a);
}
Run it and let me know
I had the same issue for these two methods:
[HttpPost]
public async Task<IActionResult> PostFoos(IEnumerable<FooModelPostDTO> requests)
[HttpPost]
public async Task<IActionResult> GetFoos(GetRequestDTO request)
The first one is for getting entities (using Post) and the second one is for posting new entities in DB (again using Post).
One possible solution is to distinguish between them by their's method names (../[action]) with the Route attribute:
[Route("api/[controller]/[action]")]
[ApiController]
public class FoosController : ControllerBase

How can I get record by Id

How can I get record by Id? I have the following code to create master detail pages in asp.net core controller, I am able to get all products using the following code and works perfect
Interface
public interface IProductService { Task<IList<ProductDTO>> GetProduct(); }
Controller Actions
public IActionResult Index()
{
return View();
}
[HttpGet]
public async Task<IActionResult> GetProducts()
{
var products = await ProductsService.GetProducts();
return Json(products);
}
But how can I get single record by Id to create a detail page.I tried this but doesn’t work
public IActionResult Detail()
{
return View();
}
[HttpGet]
public async Task<IActionResult> GetProductsDetail(int id)
{
var products = await ProductsService.GetProducts.find(id);
return Json(products);
}
GetProductCode
public class GetProducts_Action : BaseEFAction<GetProducts_Action_Request, GetProducts_Action_Response>
{
public IFileProvider FileProvider { get; }
public GetProducts_Action(ILogger<GetProducts_Action> logger, DBContext context, ITransactionManager scope, IFileProvider fileProvider) : base(logger, context, scope)
{
FileProvider = fileProvider;
}
protected override Task<GetProducts_Action_Response> PerformActionAsync(GetProducts_Action_Request request)
{
IList<ProductDTO> product;
using (var file = System.IO.File.OpenText(FileProvider.GetFileInfo("Product.json").PhysicalPath))
{
var serializer = new JsonSerializer();
product = (IList<ProductDTO>)serializer.Deserialize(file, typeof(IList<ProductDTO>));
}
return Task.FromResult(new GetProducts_Action_Response { Products = product });
}
}
public class GetProducts_Action_Request : BaseActionRequest
{
}
public class GetProducts_Action_Response : BaseActionResponse
{
public IList<ProductDTO> Products { get; set; }
}
}
Given that your data source is actually a file and not a database, you're going to be deserializing that file each time anyway. That is your performance bottleneck. So if you want you can just use your existing GetProducts() service method and then filter in the controller (using LINQ). It's not a super clean way (code-wise), but the performance will basically be the same.
[HttpGet]
public async Task<IActionResult> GetProductsDetail(int id)
{
// Get all the products and then filter by id
// change "a.Id" to the actual DTO Id property name if different
var product = (await ProductsService.GetProducts()).FirstOrDefault(a => a.Id == id);
if (product != null) {
// If we found something, return that single ProductDTO
return Json(product);
} else {
// Not Found or whatever you want
return NotFound();
}
}
FirstOrDefault() will return the first object with your desired ID (assuming the ProductDTO property is called Id). If it doesn't find anything, it will return null, so then you probably want to return a 404 Not Found or something similar.

.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();
}

Inherit http verb attributes from abstract REST controller

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();
}
}

how to add GET returns to WebApi tutorials for new classes

I am trying to find the proper way to add more "GET" return statements to my first web api project using EF and repositories. I've followed numerous examples and tutorials and they all show how to setup a web api, but now I'm trying to add to it. Example being, if I have a second class, how do I GET all values from this new class?
So far I've followed the standard examples online and everything works fine.
What I have done so far is make a second controller, then add the method names to my IRepository, and the actual code to my Repository.cs.
I just want to make sure I am doing this the proper way. Basically, should I just keep adding controllers for every class I want to return individually, then just keep making my repository longer and longer? Is that the right way to do this?
FIRST CONTROLLER (BASED ON TUTORIALS)
public class ClientController : ApiController
{
private IRepository _repo;
public ClientController(IRepository repo)
{
_repo = repo;
}
public IQueryable<Client> Get()
{
return _repo.GetAllClients();
}
public IQueryable<Client> Get(bool includeDetails)
{
IQueryable<Client> query;
if (includeDetails)
{
query = _repo.GetAllClientsWithDetails();
}
else
{
query = _repo.GetAllClients();
}
return query;
}
public Client Get(int id)
{
return _repo.GetClient(id);
}
}
IREPOSITORY
public interface IRepository
{
IQueryable<Client> GetAllClients();
IQueryable<Client> GetAllClientsWithDetails();
Client GetClient(int id);
IQueryable<Trade> GetAllTrades();
Trade GetTrade(int id);
}
REPOSITORY
public class Repository : IRepository
{
private xxxxV002Context db;
public Repository(xxxxV002Context db)
{
this.db = db;
}
//------------------------------------------------------------------------
public IQueryable<Client> GetAllClients()
{
return db.Clients;
}
public IQueryable<Client> GetAllClientsWithDetails()
{
return db.Clients.Include("Mapping_ClientAccount");
}
public Client GetClient(int id)
{
return db.Clients.Include("Mapping_ClientAccount.Account").FirstOrDefault(o => o.ClientID == id);
}
//-----------------------------------------------------------------------
public IQueryable<Trade> GetAllTrades()
{
return db.Trades;
}
public Trade GetTrade(int id)
{
return db.Trades.FirstOrDefault(x => x.TradeID == id);
}
}
SECOND CONTROLLER (NEW)
public class TradeController : ApiController
{
private IRepository _repo;
public TradeController(IRepository repo)
{
_repo = repo;
}
// GET api/<controller>
public IQueryable<Trade> Get()
{
return _repo.GetAllTrades();
}
// GET api/<controller>/5
public Trade Get(int id)
{
return _repo.GetTrade(id);
}
// POST api/<controller>
public void Post([FromBody]string value)
{
}
// PUT api/<controller>/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/<controller>/5
public void Delete(int id)
{
}
}
Just as a test I added this to ClientController, and using Routes I returned data from a different class successfully. So do I even need to have multiple controllers or can I just keep putting more methods in the one controller? What is best practice?
public class ClientController : ApiController
{
private IRepository _repo;
public ClientController(IRepository repo)
{
_repo = repo;
}
public IQueryable<Client> Get()
{
return _repo.GetAllClients();
}
public IQueryable<Client> Get(bool includeDetails)
{
IQueryable<Client> query;
if (includeDetails)
{
query = _repo.GetAllClientsWithDetails();
}
else
{
query = _repo.GetAllClients();
}
return query;
}
public Client Get(int id)
{
return _repo.GetClient(id);
}
[Route("api/client/{customerId}/orders")]
[HttpGet]
public Trade GetOrdersByCustomer(int customerId)
{
xxxxContext db = new xxxxContext();
var x1 = db.Trades.FirstOrDefault(x => x.TradeID == customerId) as xxxx.Models.Trade;
return x1;
}
}
I would echo what #LoekD said, and I would add that it is a good idea to put http verb attributes on your methods, rather than just relying on convention. It makes things clearer if your controllers get larger and it makes things more consistent.
For instance, on this method:
public IQueryable<Client> Get(bool includeDetails)
Make it:
[HttpGet]
public IQueryable<Client> Get(bool includeDetails)
It's not necessary but it will save you time in the future.
For your repository, it should really return only a specific type. Based on your code above, you would want a repository for Client objects and another repository for Trade objects.
Other than that, I think you are on the right path.
You can use the Routing feature to specify which GET operations your api supports, and which parameters it takes.
http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
For example, if you wanted to get a User by Name or by ID it could look like this:
[Route("users/{id:int}"]
public User GetUserById(int id) { ... }
[Route("users/{name}"]
public User GetUserByName(string name) { ... }
(The Route attributes are placed on your Actions)

Categories

Resources