WebAPI Controller GetAll or Get some Id - c#

I have a controller like this
[Route("api/Foo/MyController/{id}")]
public IHttpActionResult Get(int id)
{
//Code code
foo(id); // Foo accept Int or Null
}
This do work, if call api/Foo/MyController/1, but i needed call api/Foo/MyController like it "GetAll" the parameter id now is null and the stuff into controller return all, how go there?

You can add a new method and route:
[Route("api/Foo/MyController")]
public IHttpActionResult Get()
{
//Code code
}
Edit: to reuse the same method you could use optional parameters:
[Route("api/Foo/MyController/{id}")]
public IHttpActionResult Get(int? id)
{
if (id.HasValue)
{
// get by id
}
else
{
// get all
}
}

Why don't have 2 separate methods:
[Route("api/Foo/")]
public IHttpActionResult GetAll()
{
// code
}
[Route("api/Foo/{id}")]
public IHttpActionResult GetById(int id)
{
// code
}
For clarity (readability, maintainability, supportability).

You could do also a optional parameter:
[Route("api/Foo/MyController/{id}")]
public IHttpActionResult Get(int? id = null)
{
IQueryable foo = GetData();
if (id.HasValue)
{
foo = foo.Where(e => e.Id == id.Value);
}
//...
}

Related

CreatedAtAction results in "No route matches the supplied values"

There are huge numbers of questions about the "No route matches the supplied values" error, but I have not yet found any solutions among the answers :(
Here is my controller:
[ApiVersion("0.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class WidgetsController : ControllerBase
{
private readonly IRepository _repository;
public WidgetsController(IRepository repository)
{
_repository = repository;
}
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public IActionResult Add([FromBody] AddWidgetRequest request)
{
WidgetDetails details;
try
{
details = request.ToWidgetDetails();
}
catch (AddWidgetRequest.BadRequest e)
{
return BadRequest(e.Message);
}
var id = _repository.AddWidget(details);
return CreatedAtAction(nameof(GetById), new {id = id}, details.WithId(id));
}
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetById(int id)
{
if (_repository.TryGetWidget(id, out var details))
{
return Ok(details.WithId(id));
}
else
{
return NotFound();
}
}
}
When POSTing to /api/v0/Widgets, the new entry is added to the database, but HTTP 500 is returned, with message "System.InvalidOperationException: No route matches the supplied values.". My code is almost identical to the example in https://learn.microsoft.com/en-us/aspnet/core/web-api/action-return-types?view=aspnetcore-3.1, I'm at a loss as to what the issue could be.
You need specify the api version in the CreatedAtAction method like below:
public IActionResult Add([FromBody] AddWidgetRequest request,ApiVersion version)
{
return CreatedAtAction(nameof(GetById), new { id = 1, version = version.ToString() }, details.WithId(id));
}

GET action with multiple ids inputted .NET

It`s possible to create a GET action based on multiple id's inputted?
For example how can I change this method to be GetCustomer([FromRoute] int id, int code_id)?
// GET: api/Customer/5
[HttpGet("{id}")]
public async Task<IActionResult> GetCustomer([FromRoute] int id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var customerMaster = await _context.CustomerMaster.SingleOrDefaultAsync(m => m.Code == id);
if (customerMaster == null)
{
return NotFound();
}
return Ok(customerMaster);
}
Use AttributeRouting:
// GET: api/Customer/5/3
[Route("api/Customer/{id}/{code_id}")]
public async Task<IActionResult> GetCustomer(int id, int code_id)
{
...
return Ok(customer);
}

Can my BaseController have a method return a redirect or true?

That title is misleading, but I'm not sure how to word it better.
My controllers all inherit from BaseController. I would like to have a method in the BaseController that I can call from various actions. I would like something like this:
public virtual object CheckValues(Guid value1, string value2)
{
if (value2 == const_SomeValue || value1 == GetCurrentId())
{
return true;
}
return RedirectToAction("index");
}
Basically, I would like tho have a method that will check certain things and if it fails, does a Redirect. My controller action would check it like this:
public virtual ActionResult overview(Guid? id)
{
CheckValues(id, string.Empty); // on fail this redirects
// Continue with this Action
return View();
}
Many of my controller actions would make use of the CheckValues method.
Is there a good or correct way to do this?
Update: I wanted to share my solution. I liked how it came out.
My controller can now look like this:
[CheckId()] // I can overload the name of the Id, the redirect Action and/or contoller
public virtual ActionResult overview(Guid? id)
{
//... Logic for my action
return View();
}
My filter looks like this:
public class CheckIdAttribute : ActionFilterAttribute
{
public string IdValue { get; set; }
public string RedirectAction { get; set; }
public string RedirectController { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// I wanted to be able to override the redirect and
// the name of the id to check if necessary. Or just
// use defaults.
if (string.IsNullOrEmpty(IdValue))
IdValue = "id";
if (string.IsNullOrEmpty(RedirectAction))
RedirectAction = "index";
if (string.IsNullOrEmpty(RedirectController))
RedirectController = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
var isValue1Valid = filterContext.ActionParameters.ContainsKey(IdValue) &&
(filterContext.ActionParameters[IdValue] != null && (Guid)filterContext.ActionParameters[IdValue] != Guid.Empty);
if (!isValue1Valid)
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { action = RedirectAction, controller = RedirectController }));
}
}
}
An alternative to base class methods is action filters. Your controller action could look like this:
[CheckValues(Value1 = "id", Value2 = "")]
public ActionResult overview(Guid? id)
{
// Continue with this Action
return View();
}
Then in the action filter, override OnActionExecuting to check the parameters and possibly redirect.
public class CheckValuesAttribute : ActionFilterAttribute
{
public string Value1 { get; set; }
public string Value2 { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var isValue2Valid = filterContext.ActionParameters.ContainsKey(Value2) &&
filterContext.ActionParameters[Value2] == const_SomeValue;
var isValue1Valid = filterContext.ActionParameters.ContainsKey(Value1) &&
filterContext.ActionParameters[Value1] == GetCurrentId();
if (!isValue1Valid || !isValue2Valid)
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { action = "Index"}));
}
}
The above would still need some tweaking to deal with the case when Value2 is missing/empty string and casting Value1 to a Guid, but that's the gist of it. The line where you set filterContext.Result would short-circuit your action so that it actually never gets executed -- the redirect would happen before the request ever made it to your controller action.

How to Unit Test a controller that dependends on HttpContext

I want to know how to unit test my controller when it inherits from a base controller that is dependent on HttpContext. Below is my inherited controller called BaseInterimController. And below that is the AccountController method that I wish to Unit Test. We are using MOQ.
public abstract class BaseInterimController : Controller
{
#region Properties
protected string InterimName
{
get { return MultiInterim.GetInterimName(InterimIdentifier); }
}
internal virtual string InterimIdentifier
{
get { return System.Web.HttpContext.Current.Request.RequestContext.RouteData.Values["InterimIdentifier"].ToString(); }
}
}
public class AccountController : BaseInterimController
{
[HttpPost]
[AllowAnonymous]
[ValidateInput(false)]
[Route(#"{InterimIdentifier:regex([a-z]{7}\d{4})}/Account/Signin")]
public ActionResult Signin(LoginViewModel model)
{
if (ModelState.IsValid)
{
var identity = Authentication.SignIn(model.Username,
model.Password) as LegIdentity;
if (identity != null && identity.IsAuthenticated)
{
return Redirect(model.ReturnUrl);
}
else
{
// Sign in failed
ModelState.AddModelError("",
Authentication.ExternalSignInFailedMessage);
}
}
return View(model);
}
}
Coupling your controller to HttpContext can make your code very difficult to test because during unit tests HttpContext is null unless you try to mock it; which you shouldn't really do. Don't mock code you don't own.
Instead try abstracting the functionality you want to get from HttpContext into something you have control over.
this is just an example. You can try to make it even more generic if needed. I will focus on your specific scenario.
You are calling this directly in your controller
System.Web.HttpContext.Current.Request
.RequestContext.RouteData.Values["InterimIdentifier"].ToString();
When what you are really after is the ability to get that InterimIdentifier value. Something like
public interface IInterimIdentityProvider {
string InterimIdentifier { get; }
}
public class ConcreteInterimIdentityProvider : IInterimIdentityProvider {
public virtual string InterimIdentifier {
get { return System.Web.HttpContext.Current.Request.RequestContext.RouteData.Values["InterimIdentifier"].ToString(); }
}
}
which can later be implemented in a concrete class and injected into your controller provided you are using Dependency Injection.
Your base controller will then look like
public abstract class BaseInterimController : Controller {
protected IInterimIdentityProvider identifier;
public BaseInterimController(IInterimIdentityProvider identifier) {
this.identifier = identifier;
}
protected string InterimName {
get { return MultiInterim.GetInterimName(identifier.InterimIdentifier); }
}
//This can be refactored to the code above or use what you had before
//internal virtual string InterimIdentifier {
// get { return identifier.InterimIdentifier; }
//}
}
public class AccountController : BaseInterimController
{
public AccountController(IInterimIdentityProvider identifier)
: base(identifier){ }
[HttpPost]
[AllowAnonymous]
[ValidateInput(false)]
[Route(#"{InterimIdentifier:regex([a-z]{7}\d{4})}/Account/Signin")]
public ActionResult Signin(LoginViewModel model)
{
if (ModelState.IsValid)
{
var identity = Authentication.SignIn(model.Username,
model.Password) as LegIdentity;
if (identity != null && identity.IsAuthenticated)
{
return Redirect(model.ReturnUrl);
}
else
{
// Sign in failed
ModelState.AddModelError("",
Authentication.ExternalSignInFailedMessage);
}
}
return View(model);
}
}
This allows implemented controllers to not be dependent on HttpContext which will allow for better unit testing as you can easily mock/fake IInterimIdentityProvider interface using Moq to return what you want during tests.
[TestMethod]
public void Account_Controller_Should_Signin() {
//Arrange
var mock = new Mock<IInterimIdentityProvider>();
mock.Setup(m => m.InterimIdentifier).Returns("My identifier string");
var controller = new AccountController(mock.Object);
var model = new LoginViewModel() {
Username = "TestUser",
Password = ""TestPassword
};
//Act
var actionResult = controller.Signin(model);
//Assert
//...assert your expected results
}

C# WebApi refactoring Select Linq

I'm currently writing a C# Web Api in Visual Studio 2015. I'm actually copy pasting quite a lot of code.
public class APIController : ApiController
{
[HttpGet]
[Route("api/drones")]
public HttpResponseMessage getDrones()
{
var drones = db.drones.Select(d => new DroneDTO
{
iddrones = d.iddrones,
//more stuff
});
HttpResponseMessage res = Request.CreateResponse(HttpStatusCode.OK, drones);
return res;
}
[HttpGet]
[Route("api/drones/{id}")]
public HttpResponseMessage getDrones(int id)
{
var drone = db.drones.Select(d => new DroneDTO
{
iddrones = d.iddrones,
//more stuff
}).Where(drones => drones.iddrones == id);
HttpResponseMessage res = Request.CreateResponse(HttpStatusCode.OK, drone);
return res;
}
}
How should I refactor that? At first I thought about moving the var to a class member, but that doesn't seem to be allowed.
I would make a DTO factory method that worked on IQueryable<T> and then the two functions would only be responsible for creating the proper query.
This will position you better in the future when you make these functions async.
public class DroneDTO
{
public int Id { get; set; }
public static IEnumerable<DroneDTO> CreateFromQuery(IQueryable<Drone> query)
{
return query.Select(r=> new DroneDTO
{
Id = r.Id
});
}
}
public class APIController : ApiController
{
[HttpGet]
[Route("api/drones")]
public HttpResponseMessage getDrones()
{
var drones = DroneDTO.CreateFromQuery(db.drones);
HttpResponseMessage res = Request.CreateResponse(HttpStatusCode.OK, drones);
return res;
}
[HttpGet]
[Route("api/drones/{id}")]
public HttpResponseMessage getDrones(int id)
{
var drone = DroneDTO.CreateFromQuery(db.drones.Where(d => d.iddrone == id));
HttpResponseMessage res = Request.CreateResponse(HttpStatusCode.OK, drone);
return res;
}
}
I had the same problem about one year ago and unified the code by few steps:
At First, I separated my business logic from the controller in another classes. It's not one to one separation, I created class for each Entity. The other way is to using CQRS for each query/command. The general case that my business logic always returns one of this models:
public class OutputModel
{
[JsonIgnore]
public OperationResult Result { get; private set; }
public OutputDataModel(OperationResult result)
{
Result = result;
}
#region Initializatiors
public static OutputModel CreateResult(OperationResult result)
{
return new OutputModel(result);
}
public static OutputModel CreateSuccessResult()
{
return new OutputModel(OperationResult.Success);
}
#endregion Initializatiors
}
public class OutputDataModel<TData> : OutputModel
{
public TData Data { get; private set; }
public OutputDataModel(OperationResult result)
: base(result)
{
}
public OutputDataModel(OperationResult result, TData data)
: this(result)
{
Data = data;
}
#region Initializatiors
public static OutputDataModel<TData> CreateSuccessResult(TData data)
{
return new OutputDataModel<TData>(OperationResult.Success, data);
}
public static OutputDataModel<TData> CreateResult(OperationResult result, TData data)
{
return new OutputDataModel<TData>(result, data);
}
public new static OutputDataModel<TData> CreateResult(OperationResult result)
{
return new OutputDataModel<TData>(result);
}
#endregion Initializatiors
}
Operation result is an Enumeration that contains something like StatusCode in a platform independent style:
public enum OperationResult
{
AccessDenied,
BadRequest,
Conflict,
NotFound,
NotModified,
AccessDenied,
Created,
Success
}
It allowed me to handle all web api calls on the same manner and uses my business logic not only in web api but in other clients (for example, I created small WPF app which uses my business logic classes to display operational information).
I created base API controller that handle OutputDataModel to compose response:
public class RikropApiControllerBase : ApiController
{
#region Result handling
protected HttpResponseMessage Response(IOutputModel result, HttpStatusCode successStatusCode = HttpStatusCode.OK)
{
switch (result.Result)
{
case OperationResult.AccessDenied:
return Request.CreateResponse(HttpStatusCode.Forbidden);
case OperationResult.BadRequest:
return Request.CreateResponse(HttpStatusCode.BadRequest);
case OperationResult.Conflict:
return Request.CreateResponse(HttpStatusCode.Conflict);
case OperationResult.NotFound:
return Request.CreateResponse(HttpStatusCode.NotFound);
case OperationResult.NotModified:
return Request.CreateResponse(HttpStatusCode.NotModified);
case OperationResult.Created:
return Request.CreateResponse(HttpStatusCode.Created);
case OperationResult.Success:
return Request.CreateResponse(successStatusCode);
default:
return Request.CreateResponse(HttpStatusCode.NotImplemented);
}
}
protected HttpResponseMessage Response<TData>(IOutputDataModel<TData> result, HttpStatusCode successStatusCode = HttpStatusCode.OK)
{
switch (result.Result)
{
case OperationResult.AccessDenied:
return Request.CreateResponse(HttpStatusCode.Forbidden);
case OperationResult.BadRequest:
return Request.CreateResponse(HttpStatusCode.BadRequest);
case OperationResult.Conflict:
return Request.CreateResponse(HttpStatusCode.Conflict);
case OperationResult.NotFound:
return Request.CreateResponse(HttpStatusCode.NotFound);
case OperationResult.NotModified:
return Request.CreateResponse(HttpStatusCode.NotModified, result.Data);
case OperationResult.Created:
return Request.CreateResponse(HttpStatusCode.Created, result.Data);
case OperationResult.Success:
return Request.CreateResponse(successStatusCode, result.Data);
default:
return Request.CreateResponse(HttpStatusCode.NotImplemented);
}
}
#endregion Result handling
}
Now my api controllers almost did not contain the code! Look at the example with really heavy controller:
[RoutePrefix("api/ShoppingList/{shoppingListId:int}/ShoppingListEntry")]
public class ShoppingListEntryController : RikropApiControllerBase
{
private readonly IShoppingListService _shoppingListService;
public ShoppingListEntryController(IShoppingListService shoppingListService)
{
_shoppingListService = shoppingListService;
}
[Route("")]
[HttpPost]
public HttpResponseMessage AddNewEntry(int shoppingListId, SaveShoppingListEntryInput model)
{
model.ShoppingListId = shoppingListId;
var result = _shoppingListService.SaveShoppingListEntry(model);
return Response(result);
}
[Route("")]
[HttpDelete]
public HttpResponseMessage ClearShoppingList(int shoppingListId)
{
var model = new ClearShoppingListEntriesInput {ShoppingListId = shoppingListId, InitiatorId = this.GetCurrentUserId()};
var result = _shoppingListService.ClearShoppingListEntries(model);
return Response(result);
}
[Route("{shoppingListEntryId:int}")]
public HttpResponseMessage Put(int shoppingListId, int shoppingListEntryId, SaveShoppingListEntryInput model)
{
model.ShoppingListId = shoppingListId;
model.ShoppingListEntryId = shoppingListEntryId;
var result = _shoppingListService.SaveShoppingListEntry(model);
return Response(result);
}
[Route("{shoppingListEntry:int}")]
public HttpResponseMessage Delete(int shoppingListId, int shoppingListEntry)
{
var model = new DeleteShoppingListEntryInput
{
ShoppingListId = shoppingListId,
ShoppingListEntryId = shoppingListEntry,
InitiatorId = this.GetCurrentUserId()
};
var result = _shoppingListService.DeleteShoppingListEntry(model);
return Response(result);
}
}
I added an extension method to get current user credentials GetCurrentUserId. If method parameters contains a class that implements IAuthorizedInput that contains 1 property with USerId then I added this info in a global filter. In other cases I need to add this manually. GetCurrentUserId is depend on your authorization method.
It's just a code style, but I called all input models for my business logic with Input suffix (see examples above: DeleteShoppingListEntryInput, ClearShoppingListEntriesInput, SaveShoppingListEntryInput) and result models with output syntax (it's interesting that you no need to declare this types in controller because it's a part of generic class OutputDataModel<TData>).
I'm also used AutoMapper to map my entities to Ouput-classes instead of tons of CreateFromEntity methods.
I'm using an abstraction for data source. In my scenario it was
Repository but this solution has no English documentation then
the better way is to use one of more common solutions.
I also had a base class for my business logic that helps me to create output-models:
public class ServiceBase
{
#region Output parameters
public IOutputDataModel<TData> SuccessOutput<TData>(TData data)
{
return OutputDataModel<TData>.CreateSuccessResult(data);
}
public IOutputDataModel<TData> Output<TData>(OperationResult result, TData data)
{
return OutputDataModel<TData>.CreateResult(result, data);
}
public IOutputDataModel<TData> Output<TData>(OperationResult result)
{
return OutputDataModel<TData>.CreateResult(result);
}
public IOutputModel SuccessOutput()
{
return OutputModel.CreateSuccessResult();
}
public IOutputModel Output(OperationResult result)
{
return OutputModel.CreateResult(result);
}
#endregion Output parameters
}
Finally my "services" with business logic looks like similar to each other. Lets look an example:
public class ShoppingListService : ServiceBase, IShoppingListService
{
private readonly IRepository<ShoppingList, int> _shoppingListRepository;
private readonly IRepository<ShoppingListEntry, int> _shoppingListEntryRepository;
public ShoppingListService(IRepository<ShoppingList, int> shoppingListRepository,
IRepository<ShoppingListEntry, int> shoppingListEntryRepository)
{
_shoppingListRepository = shoppingListRepository;
_shoppingListEntryRepository = shoppingListEntryRepository;
}
public IOutputDataModel<ListModel<ShoppingListDto>> GetUserShoppingLists(GetUserShoppingListsInput model)
{
var shoppingLists =
_shoppingListRepository.Get(q => q.Filter(sl => sl.OwnerId == model.InitiatorId).Include(sl => sl.Entries));
return SuccessOutput(new ListModel<ShoppingListDto>(Mapper.Map<IEnumerable<ShoppingList>, ShoppingListDto[]>(shoppingLists)));
}
public IOutputDataModel<GetShoppingListOutputData> GetShoppingList(GetShoppingListInput model)
{
var shoppingList =
_shoppingListRepository
.Get(q => q.Filter(sl => sl.Id == model.ShoppingListId).Include(sl => sl.Entries).Take(1))
.SingleOrDefault();
if (shoppingList == null)
return Output<GetShoppingListOutputData>(OperationResult.NotFound);
if (shoppingList.OwnerId != model.InitiatorId)
return Output<GetShoppingListOutputData>(OperationResult.AccessDenied);
return
SuccessOutput(new GetShoppingListOutputData(Mapper.Map<ShoppingListDto>(shoppingList),
Mapper.Map<IEnumerable<ShoppingListEntry>, List<ShoppingListEntryDto>>(shoppingList.Entries)));
}
public IOutputModel DeleteShoppingList(DeleteShoppingListInput model)
{
var shoppingList = _shoppingListRepository.Get(model.ShoppingListId);
if (shoppingList == null)
return Output(OperationResult.NotFound);
if (shoppingList.OwnerId != model.InitiatorId)
return Output(OperationResult.AccessDenied);
_shoppingListRepository.Delete(shoppingList);
return SuccessOutput();
}
public IOutputModel DeleteShoppingListEntry(DeleteShoppingListEntryInput model)
{
var entry =
_shoppingListEntryRepository.Get(
q => q.Filter(e => e.Id == model.ShoppingListEntryId).Include(e => e.ShoppingList).Take(1))
.SingleOrDefault();
if (entry == null)
return Output(OperationResult.NotFound);
if (entry.ShoppingList.OwnerId != model.InitiatorId)
return Output(OperationResult.AccessDenied);
if (entry.ShoppingListId != model.ShoppingListId)
return Output(OperationResult.BadRequest);
_shoppingListEntryRepository.Delete(entry);
return SuccessOutput();
}
public IOutputModel ClearShoppingListEntries(ClearShoppingListEntriesInput model)
{
var shoppingList =
_shoppingListRepository.Get(
q => q.Filter(sl => sl.Id == model.ShoppingListId).Include(sl => sl.Entries).Take(1))
.SingleOrDefault();
if (shoppingList == null)
return Output(OperationResult.NotFound);
if (shoppingList.OwnerId != model.InitiatorId)
return Output(OperationResult.AccessDenied);
if (shoppingList.Entries != null)
_shoppingListEntryRepository.Delete(shoppingList.Entries.ToList());
return SuccessOutput();
}
private IOutputDataModel<int> CreateShoppingList(SaveShoppingListInput model)
{
var shoppingList = new ShoppingList
{
OwnerId = model.InitiatorId,
Title = model.ShoppingListTitle,
Entries = model.Entries.Select(Mapper.Map<ShoppingListEntry>).ForEach(sle => sle.Id = 0).ToList()
};
shoppingList = _shoppingListRepository.Save(shoppingList);
return Output(OperationResult.Created, shoppingList.Id);
}
}
Now all routine of creating DTOs, responses and other nonBusinessLogic actions are in the base classes and we can add features in a easiest and clear way. For new Entity creates new "service" (repository will be created automatically in a generic manner) and inherit it from service base. For new action add a method to existing "service" and actions in API. That is all.
It's just a recommendation that not related with question but it is very useful for me to check routings with auto-generated help page. I also used simple client to execute web api queries from the help page.
My results:
Platform-independent & testable business logic layer;
Map business logic result to HttpResponseMessage in base class in a generic manner;
Half-automated security with ActionFilterAttribute;
"Empty" controllers;
Readable code (code conventions and model hierarchy);
I'd suggest to go with the Repository Pattern. Here you have - IMO - an excellent article about it. This should be one of the easiest refactoring you can do.
Following the guidelines from the indicated article you could refactor the code like the following:
Create the base repository interface
public interface IRepository<TEntity, in TKey> where TEntity : class
{
TEntity Get(TKey id);
void Save(TEntity entity);
void Delete(TEntity entity);
}
Create the specialized repository interface:
public interface IDroneDTORepository : IRepository<DroneDTO, int>
{
IEnumerable<DroneDTO> FindAll();
IEnumerable<DroneDTO> Find(int id);
}
Implement the specialized repository interface:
public class DroneDTORepository : IDroneDTORepository
{
private readonly DbContext _dbContext;
public DroneDTORepository(DbContext dbContext)
{
_dbContext = dbContext;
}
public DroneDTO Get(int id)
{
return _dbContext.DroneDTOs.FirstOrDefault(x => x.Id == id);
}
public void Save(DroneDTO entity)
{
_dbContext.DroneDTOs.Attach(entity);
}
public void Delete(DroneDTO entity)
{
_dbContext.DroneDTOs.Remove(entity);
}
public IEnumerable<DroneDTO> FindAll()
{
return _dbContext.DroneDTOs
.Select(d => new DroneDTO
{
iddrones = d.iddrones,
//more stuff
})
.ToList();
}
public IEnumerable<DroneDTO> Find(int id)
{
return FindAll().Where(x => x.iddrones == id).ToList();
}
}
Use the repository in the code:
private IDroneDTORepository _repository = new DroneDTORepository(dbContext);
[HttpGet]
[Route("api/drones")]
public HttpResponseMessage getDrones()
{
var drones = _repository.FindAll();
HttpResponseMessage res = Request.CreateResponse(HttpStatusCode.OK, drones);
return res;
}
[HttpGet]
[Route("api/drones/{id}")]
public HttpResponseMessage getDrones(int id)
{
var drone = _repository.Find(id);
HttpResponseMessage res = Request.CreateResponse(HttpStatusCode.OK, drone);
return res;
}
This should be close to the resulting code (obviously something might need changes). Let me know if anything is unclear.
Reusing the Select part (projection) in such scenarios is quite easy.
Let take a look at Queryable.Select method signature
public static IQueryable<TResult> Select<TSource, TResult>(
this IQueryable<TSource> source,
Expression<Func<TSource, TResult>> selector
)
What you call "selection code" is actually the selector parameter. Assuming your entity class is called Drone, then according to the above definition we can extract that part as Expression<Func<Drone, DroneDto>> and reuse it in both places like this
public class APIController : ApiController
{
static Expression<Func<Drone, DroneDto>> ToDto()
{
// The code that was inside Select(...)
return d => new DroneDTO
{
iddrones = d.iddrones,
//more stuff
};
}
[HttpGet]
[Route("api/drones")]
public HttpResponseMessage getDrones()
{
var drones = db.drones.Select(ToDto());
HttpResponseMessage res = Request.CreateResponse(HttpStatusCode.OK, drones);
return res;
}
[HttpGet]
[Route("api/drones/{id}")]
public HttpResponseMessage getDrones(int id)
{
var drone = db.drones.Where(d => d.iddrones == id).Select(ToDto());
HttpResponseMessage res = Request.CreateResponse(HttpStatusCode.OK, drone);
return res;
}
}
Of course these two methods can further be refactored (to become "one liners"), but the above is the minimal refactoring that allows reusing the Select part w/o changing any semantics, executing context or the way you write your queries.
Put your mapping to DTO code into a single method that you reuse then you can just do something like:
var drone = db.drones.Select(d => DroneDto.FromDb(d))
.Where(drones => drones.iddrones == id);
public class DroneDto
{
public int iddrones {get;set;}
// ...other props
public static DroneDto FromDb(DroneEntity dbEntity)
{
return new DroneDto
{
iddrones = dbEntity.iddrones,
//... other props
}
}
}
First, try avoid use db directly in the webapi, move to a service.
And second, if I've understand your question, you want avoid write the conversion. You can use AutoMapper, install via nuget with extensions AutoMapper.QueryableExtensions, and configure the mapping between Drone and DroneDto. Configure the mapper:
Mapper.CreateMap<Drone, Dtos.DroneDTO>();
And use as simple as:
db.Drones
.Where(d => ... condition ...)
.Project()
.To<DroneDto>()
.ToList();
Like ben did, you can put your conversion code into a static method on the DroneDto class as such:
public class DroneDto
{
public int iddrones {get;set;}
public static DroneDto CreateFromEntity(DroneEntity dbEntity)
{
return new DroneDto
{
iddrones = dbEntity.iddrones,
...
};
}
}
However, the problem with Bens approach was that the .Select method was called on the DbSet, and LINQ to Entities do not handle these methods. So, you need to do your queries on the DbSet first, then collect the result. For example by calling .ToList(). Then you can do the conversion.
public class APIController : ApiController
{
[HttpGet]
[Route("api/drones")]
public HttpResponseMessage getDrones()
{
var drones = db.drones.ToList().Select(d => DroneDto.CreateFromEntity(d));
HttpResponseMessage res = Request.CreateResponse(HttpStatusCode.OK, drones);
return res;
}
[HttpGet]
[Route("api/drones/{id}")]
public HttpResponseMessage getDrones(int id)
{
var drone = db.drones.Where(d => d.iddrone == id)
.ToList().Select(d => DroneDto.CreateFromEntity(d));
HttpResponseMessage res = Request.CreateResponse(HttpStatusCode.OK, drone);
return res;
}
}
Alternatively, if you want to avoid multiple enumerations of the result, have a look at AutoMapper. Specifically the Queryable-Extensions.
The DB Call should be in a separate layer to the web api (reason: separation of concerns: you may want to change the DB technology in the future, and your web API may want to get data from other sources)
Use a factory to build your DroneDTO. If you are using dependency injection, you can inject it into the web api controller. If this factory is simple (is not depended on by other factories) you can get away with making it static, but be careful with this: you don't want to have lots of static factories that depend on each other because as soon as one needs to not be static any more you will have to change all of them.
public class APIController : ApiController
{
private readonly IDroneService _droneService;
public APIController(IDroneService droneService)
{
_droneService = droneService;
}
[HttpGet]
[Route("api/drones")]
public HttpResponseMessage GetDrones()
{
var drones = _droneService
.GetDrones()
.Select(DroneDTOFactory.Build);
return Request.CreateResponse(HttpStatusCode.OK, drones);
}
[HttpGet]
[Route("api/drones/{id}")]
public HttpResponseMessage GetDrones(int id)
{
// I am assuming you meant to get a single drone here
var drone = DroneDTOFactory.Build(_droneService.GetDrone(id));
return Request.CreateResponse(HttpStatusCode.OK, drone);
}
}
public static class DroneDTOFactory
{
public static DroneDTO Build(Drone d)
{
if (d == null)
return null;
return new DroneDTO
{
iddrones = d.iddrones,
//more stuff
};
}
}
Use a separate data access layer. I assumed the GetDrone(int Id) will retrieve one or no drone and used SingleOrDefault(). You can adjust that as needed.
//move all the db access stuff here
public class Db
{
//assuming single drone is returned
public Drone GetDrone(int id)
{
//do SingleOrDefault or Where depending on the needs
Drone drone = GetDrones().SingleOrDefault(drones => drones.iddrones == id);
return drone;
}
public IQueryable<Drone> GetDrones()
{
var drone = db.drones.Select(d => new DroneDTO
{
iddrones = d.iddrones,
//more stuff
});
return drone;
}
}
Then from the client:
public class APIController : ApiController
{
//this can be injected, service located, etc. simple instance in this eg.
private Db dataAccess = new Db();
[HttpGet]
[Route("api/drones")]
public HttpResponseMessage getDrones()
{
var drones = dataAccess.GetDrones();
HttpResponseMessage res = Request.CreateResponse(HttpStatusCode.OK, drones);
return res;
}
[HttpGet]
[Route("api/drones/{id}")]
public HttpResponseMessage getDrones(int id)
{
var drone = dataAccess.GetDrone(int id);
HttpResponseMessage res = Request.CreateResponse(HttpStatusCode.OK, drone);
return res;
}
}

Categories