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;
}
}
Related
If I have a controller with quite a few actions, doing similar things e.g. Gets data from an api, does something with it, returns view with updated model. Is there a better way to handle errors. Currently I do this on a number of action methods, obviously this duplication doesn't feel right but I can't think of an alternative. Thanks
public async Task<IActionResult> method(string id)
{
var result = await _flightRepository.GetLightById(id);
if (!result.Valid)
{
return View("ErrorPage", result.Error.Message);
}
var viewModel = new FlightViewModel
{
Flight = result.Result
};
return View(viewModel);
}
Basically I want to somehow encapsulate the error handling logic to return the error view if valid is false, otherwise populate viewmodel and return view. The valid property returns true if there is an error with the request (this is done in api layer)
Thanks for any help
You should use Filters to reuse code among your actions.
For example in your case your filter could be:
public class FlightValidator: ActionFilterAttribute
{
private readonly string _flightIdRouteKey; // e.g 23
private readonly string _errorViewName; // e.g "ErrorPage"
private readonly IFlightRepository _flightRepo;
public FlightValidator(string flightIdRouteKey, string errorViewName, IFlightRepository flightRepository)
{
_flightIdRouteKey = flightIdRouteKey;
_errorViewName = errorViewName;
_flightRepo = flightRepository;
}
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
int flightId = (int)context.RouteData.Values[_flightIdRouteKey];
var result = await _flightRepo.GetFlightById(flightId);
if (!result.Valid)
{
context.Result = new ViewResult
{
ViewName = _errorViewName,
ViewData = new ViewDataDictionary(result.ViewData)
{
Model = model
}
};
return;
}
await next();
}
}
Now you can use the filter like this:
[TypeFilter(typeof(FlightValidator), Arguments = new object[] { "id", "ErrorPage"})]
public async Task<IActionResult> method(string id)
{
var viewModel = new FlightViewModel
{
Flight = result.Result
};
return View(viewModel);
}
I suggest you to refer Filters docs for more information about filters.
If you use a static method like this:
static async Task<IActionResult> ProcessAsync<T>(
Func<Task<RepositoryResult<T>>> process,
Func<T, IActionResult> ifValid
)
{
var result = await process();
if (!result.Valid)
{
return View("ErrorPage", result.Error.Message);
}
return ifValid(result.Result);
}
Then you can use it like this:
public Task<IActionResult> method(string id)
{
return ProcessAsync(
() => _flightRepository.GetLightById(id),
flight =>
{
var viewModel = new FlightViewModel
{
Flight = flight
};
return View(viewModel);
}
);
}
The static method can be defined anywhere you wish - in a base Controller class, or in a different namespace entirely (in which case you would import it with using static Some.Namespace.With.Class;.
Additionally, I suggest you use the convention of an Async suffix for asynchronous methods - so because GetLightById returns Task<T> I suggest renaming it GetLightByIdAsync.
In our application we are using mediatr and there is a common pattern as follows:
class SomeController
{
public Action Foo(SomeRequest request)
{
var result = Mediatr.Send(request);
if(result == null)
{
return NotFound();
}
return Ok(result);
}
}
This code repeats for every API end point, regardless of the HTTP method.
I read about API conventions but I guess that is about Swagger, API analyser and such.
How can I avoid having this repetetive code above?
class BaseController
{
protected static IActionResult GenericAction(object request)
{
var result = Mediatr.Send(request);
if(result == null)
{
return NotFound();
}
return Ok(result);
}
}
Then
class SomeController : BaseController
{
public Action Foo(SomeRequest request)
{
return GenericAction(request);
}
}
if your methods always has same structure you can even generalize it more
class BaseController<TRequest>
{
public virtual Action Foo(SomeRequest request)
{
return GenericAction(request);
}
protected static IActionResult GenericAction(TRequest request)
{
var result = Mediatr.Send(request);
if(result == null)
{
return NotFound();
}
return Ok(result);
}
}
Then
class SomeController<SomeRequest> : BaseController
{
}
I've got a pretty basic controller method that returns a list of Customers. I want it to return the List View when a user browses to it, and return JSON to requests that have application/json in the Accept header.
Is that possible in ASP.NET Core MVC 1.0?
I've tried this:
[HttpGet("")]
public async Task<IActionResult> List(int page = 1, int count = 20)
{
var customers = await _customerService.GetCustomers(page, count);
return Ok(customers.Select(c => new { c.Id, c.Name }));
}
But that returns JSON by default, even if it's not in the Accept list. If I hit "/customers" in my browser, I get the JSON output, not my view.
I thought I might need to write an OutputFormatter that handled text/html, but I can't figure out how I can call the View() method from an OutputFormatter, since those methods are on Controller, and I'd need to know the name of the View I wanted to render.
Is there a method or property I can call to check if MVC will be able to find an OutputFormatter to render? Something like the following:
[HttpGet("")]
public async Task<IActionResult> List(int page = 1, int count = 20)
{
var customers = await _customerService.GetCustomers(page, count);
if(Response.WillUseContentNegotiation)
{
return Ok(customers.Select(c => new { c.Id, c.Name }));
}
else
{
return View(customers.Select(c => new { c.Id, c.Name }));
}
}
I think this is a reasonable use case as it would simplify creating APIs that return both HTML and JSON/XML/etc from a single controller. This would allow for progressive enhancement, as well as several other benefits, though it might not work well in cases where the API and Mvc behavior needs to be drastically different.
I have done this with a custom filter, with some caveats below:
public class ViewIfAcceptHtmlAttribute : Attribute, IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
if (context.HttpContext.Request.Headers["Accept"].ToString().Contains("text/html"))
{
var originalResult = context.Result as ObjectResult;
var controller = context.Controller as Controller;
if(originalResult != null && controller != null)
{
var model = originalResult.Value;
var newResult = controller.View(model);
newResult.StatusCode = originalResult.StatusCode;
context.Result = newResult;
}
}
}
public void OnActionExecuting(ActionExecutingContext context)
{
}
}
which can be added to a controller or action:
[ViewIfAcceptHtml]
[Route("/foo/")]
public IActionResult Get(){
return Ok(new Foo());
}
or registered globally in Startup.cs
services.AddMvc(x=>
{
x.Filters.Add(new ViewIfAcceptHtmlAttribute());
});
This works for my use case and accomplishes the goal of supporting text/html and application/json from the same controller. I suspect isn't the "best" approach as it side-steps the custom formatters. Ideally (in my mind), this code would just be another Formatter like Xml and Json, but that outputs Html using the View rendering engine. That interface is a little more involved, though, and this was the simplest thing that works for now.
I haven't tried this, but could you just test for that content type in the request and return accordingly:
var result = customers.Select(c => new { c.Id, c.Name });
if (Request.Headers["Accept"].Contains("application/json"))
return Json(result);
else
return View(result);
I liked Daniel's idea and felt inspired, so here's a convention based approach as well. Because often the ViewModel needs to include a little bit more 'stuff' than just the raw data returned from the API, and it also might need to check different stuff before it does its work, this will allow for that and help in following a ViewModel for every View principal. Using this convention, you can write two controller methods <Action> and <Action>View both of which will map to the same route. The constraint applied will choose <Action>View if "text/html" is in the Accept header.
public class ContentNegotiationConvention : IActionModelConvention
{
public void Apply(ActionModel action)
{
if (action.ActionName.ToLower().EndsWith("view"))
{
//Make it match to the action of the same name without 'view', exa: IndexView => Index
action.ActionName = action.ActionName.Substring(0, action.ActionName.Length - 4);
foreach (var selector in action.Selectors)
//Add a constraint which will choose this action over the API action when the content type is apprpriate
selector.ActionConstraints.Add(new TextHtmlContentTypeActionConstraint());
}
}
}
public class TextHtmlContentTypeActionConstraint : ContentTypeActionConstraint
{
public TextHtmlContentTypeActionConstraint() : base("text/html") { }
}
public class ContentTypeActionConstraint : IActionConstraint, IActionConstraintMetadata
{
string _contentType;
public ContentTypeActionConstraint(string contentType)
{
_contentType = contentType;
}
public int Order => -10;
public bool Accept(ActionConstraintContext context) =>
context.RouteContext.HttpContext.Request.Headers["Accept"].ToString().Contains(_contentType);
}
which is added in startup here:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(o => { o.Conventions.Add(new ContentNegotiationConvention()); });
}
In you controller, you can write method pairs like:
public class HomeController : Controller
{
public ObjectResult Index()
{
//General checks
return Ok(new IndexDataModel() { Property = "Data" });
}
public ViewResult IndexView()
{
//View specific checks
return View(new IndexViewModel(Index()));
}
}
Where I've created ViewModel classes meant to take the output of API actions, another pattern which connects the API to the View output and reinforces the intent that these two represent the same action:
public class IndexViewModel : ViewModelBase
{
public string ViewOnlyProperty { get; set; }
public string ExposedDataModelProperty { get; set; }
public IndexViewModel(IndexDataModel model) : base(model)
{
ExposedDataModelProperty = model?.Property;
ViewOnlyProperty = ExposedDataModelProperty + " for a View";
}
public IndexViewModel(ObjectResult apiResult) : this(apiResult.Value as IndexDataModel) { }
}
public class ViewModelBase
{
protected ApiModelBase _model;
public ViewModelBase(ApiModelBase model)
{
_model = model;
}
}
public class ApiModelBase { }
public class IndexDataModel : ApiModelBase
{
public string Property { get; internal set; }
}
I'm trying to Setup a Mock call to a the IModelFactory interface that I have.
here is the IModelFactory interface
public interface IModelFactory
{
MaterialAcceptedModel Create(MaterialAccepted model);
MaterialAccepted Parse(MaterialAcceptedModel model);
}
This is the ModelFactory class which implements the IModelFactor interface (I've only add here the method I'm trying to test, no need to add the implementation of the Create Method)
public class ModelFactory : IModelFactory
{
private UrlHelper _urlHelper;
private IRTWRepository _repo;
//private IKeysGeneratorService _keysGen;
private IGeocodeService _geocoder;
public ModelFactory(HttpRequestMessage request, IRTWRepository repo,IGeocodeService geocoder)
{
_urlHelper = new UrlHelper(request);
_repo = repo;
_geocoder = geocoder;
}
#region Parses
public MaterialAccepted Parse(MaterialAcceptedModel model)
{
try
{
if (!string.IsNullOrWhiteSpace(model.category))
{
var category = _repo.CategoryRepository.Get(model.category);
if (category == null) return null;
var entry = new MaterialAccepted()
{
business = model.business,
businessService = model.businessService,
residential = model.residential,
residentialService = model.residentialService,
note = model.note,
Category = category
};
return entry;
}
return null;
}
catch
{
return null;
}
}
#endregion
}
I'm using a BaseAPiController that contains the repo and configuration Interfaces
public class BaseApiController : ApiController
{
IRTWRepository _repository;
IModelFactory _modelFactory;
IConfiguration _configuration;
IGeocodeService _geocoder;
public BaseApiController(IRTWRepository repository,IConfiguration configuration)
{
_repository = repository;
_configuration = configuration;
}
protected IRTWRepository TheRepository
{
get
{
return _repository;
}
}
protected IConfiguration TheConfiguration
{
get
{
return _configuration;
}
}
protected IModelFactory TheModelFactory
{
get
{
_geocoder = new GeocodeService(_configuration.GetValue("geocodeGoogleApiKey"));
if (_modelFactory == null)
{
_modelFactory = new ModelFactory(this.Request, _repository,_geocoder);
}
return _modelFactory;
}
}
Here is the action method in the controller I'm trying to test
[HttpPost]
[Route("api/recyclecenters/{rcid}/materials/")]
public IHttpActionResult Post(int rcid, [FromBody]MaterialAcceptedModel model)
{
try
{
if (model != null)
{
var recycleCenter = TheRepository.RecycleCenterRepository.Get(rcid);
if (recycleCenter == null)
return NotFound();
if (!ModelState.IsValid)
return BadRequest(ModelState);
var entity = TheModelFactory.Parse(model);
if (entity == null) return BadRequest("Could not read material accepted in body");
if (TheRepository.MaterialAcceptedRepository.Get(recycleCenter.RecycleCenterId, entity.Category.name) != null)
return Conflict();
recycleCenter.Materials.Add(entity);
if (TheRepository.SaveAll())
{
string locationHeader = Url.Link("Materials", new { rcid = rcid, name = model.category.ToLower() });
return Created<MaterialAcceptedModel>(locationHeader, TheModelFactory.Create(entity));
}
return BadRequest("Could not save to the database");
}
return BadRequest();
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
This is the line that returns null even though I mocked it up on my test method
var entity = TheModelFactory.Parse(model);
and this one is my TestClass
namespace API.Tests.Web
{
[TestClass]
public class MaterialsControllerTest
{
private Mock<IRTWRepository> repository;
private Mock<IModelFactory> factory;
private Mock<IConfiguration> configuration;
private Mock<IRTWAPIIdentityService> identityService;
private MaterialsController controller;
RecycleCenter recycleCenter;
private MaterialAccepted CreateMaterial()
{
return new MaterialAccepted()
{
business = true,
businessService = EnumRecycleCenterService.Dropoff,
residential = false,
residentialService = EnumRecycleCenterService.Pickup,
note = "this a note",
Category = new Category()
{
name = "Books"
}
};
}
[TestInitialize]
public void Initialize()
{
repository = new Mock<IRTWRepository>();
factory = new Mock<IModelFactory>();
configuration = new Mock<IConfiguration>();
identityService = new Mock<IRTWAPIIdentityService>();
controller = new MaterialsController(repository.Object,configuration.Object);
controller.Request = new HttpRequestMessage();
recycleCenter = new RecycleCenter(){RecycleCenterId = 1};
}
[TestMethod]
public void Post_ShouldReturnConflictIfTheRecycleCenterAlreadyTakesMaterial()
{
//arrange
repository.Setup(r => r.RecycleCenterRepository.Get(It.IsAny<int>())).Returns(() => recycleCenter);
factory.Setup(f => f.Parse(new MaterialAcceptedModel())).Returns(() => new MaterialAccepted());
configuration.Setup(c => c.GetValue(It.IsAny<string>())).Returns(() => "APIKEY");
repository.Setup(r => r.MaterialAcceptedRepository.Get(It.IsAny<int>(), It.IsAny<string>())).Returns(() => null);
//act
var actionResult = controller.Post(It.IsAny<int>(),new MaterialAcceptedModel());
//assert
Assert.IsInstanceOfType(actionResult, typeof(ConflictResult));
}
}
}
This is the line that's not working because it always return null instead of a new instance of MaterialAccepted
factory.Setup(f => f.Parse(new MaterialAcceptedModel())).Returns(() => new MaterialAccepted());
I tried f.Parse(It.IsAny()) but still doesn't work.
To clarify
the above line of code is returning null because is not mocking the f.Parse() call, instead is executing it and returning null because the if condition I have on that method
Anyone could explain why the Setup is not working?
Setting up your Mock using It.IsAny will work:
factory.Setup(f => f.Parse(It.IsAny<MaterialAcceptedModel>()))
.Returns(() => new MaterialAccepted());
However, as has been said by #Macilquham, I can't see where your are passing the Mock to your controller in the supplied code so that it is used by the production code.
If you don't call the method on your Mock object, which you don't, you're currently calling the method on the instance of the real object created by your base class, then it doesn't matter how you set up your mock it's not going to work. If you are able to change your base class, then doing something like this would allow you to work around your problem:
// Add defaulted parameter to base class to allow modelFactory creation
// to be overridden/injected (this will prevent the current default behaviour
// when fetching the factory, if a non-null is passed in)
public BaseApiController(IRTWRepository repository,IConfiguration configuration,
IModelFactory modelFactory = null)
{
_modelFactory = modelFactory;
}
Modify your sut constructor to allow you to supply a modelFactory (again, default it to null), then amend your test as appropriate:
controller = new MaterialsController(repository.Object,configuration.Object,
factory.Object);
You don't seem to be injecting in the IModelFactory into the controller. You need to make sure that your production code is using the Mock you are setting up in the test.
Mock cannot return null directly.
The trick is just to create a null object.
Assuming the object returned is of type class Material:
Material nullMaterial = null;
...
repository.Setup(r => r.MaterialAcceptedRepository
.Get(It.IsAny<int>(), It.IsAny<string>()))
.Returns(nullMaterial);
This should solve your problem
I'm trying to return a status code of 304 not modified for a GET method in a web api controller.
The only way I succeeded was something like this:
public class TryController : ApiController
{
public User GetUser(int userId, DateTime lastModifiedAtClient)
{
var user = new DataEntities().Users.First(p => p.Id == userId);
if (user.LastModified <= lastModifiedAtClient)
{
throw new HttpResponseException(HttpStatusCode.NotModified);
}
return user;
}
}
The problem here is that it's not an exception, It's just not modified so the client cache is OK.
I also want the return type to be a User (as all the web api examples shows with GET) not return HttpResponseMessage or something like this.
I did not know the answer so asked the ASP.NET team here.
So the trick is to change the signature to HttpResponseMessage and use Request.CreateResponse.
[ResponseType(typeof(User))]
public HttpResponseMessage GetUser(HttpRequestMessage request, int userId, DateTime lastModifiedAtClient)
{
var user = new DataEntities().Users.First(p => p.Id == userId);
if (user.LastModified <= lastModifiedAtClient)
{
return new HttpResponseMessage(HttpStatusCode.NotModified);
}
return request.CreateResponse(HttpStatusCode.OK, user);
}
You can also do the following if you want to preserve the action signature as returning User:
public User GetUser(int userId, DateTime lastModifiedAtClient)
If you want to return something other than 200 then you throw an HttpResponseException in your action and pass in the HttpResponseMessage you want to send to the client.
Change the GetXxx API method to return HttpResponseMessage and then return a typed version for the full response and the untyped version for the NotModified response.
public HttpResponseMessage GetComputingDevice(string id)
{
ComputingDevice computingDevice =
_db.Devices.OfType<ComputingDevice>()
.SingleOrDefault(c => c.AssetId == id);
if (computingDevice == null)
{
return this.Request.CreateResponse(HttpStatusCode.NotFound);
}
if (this.Request.ClientHasStaleData(computingDevice.ModifiedDate))
{
return this.Request.CreateResponse<ComputingDevice>(
HttpStatusCode.OK, computingDevice);
}
else
{
return this.Request.CreateResponse(HttpStatusCode.NotModified);
}
}
*The ClientHasStale data is my extension for checking ETag and IfModifiedSince headers.
The MVC framework should still serialize and return your object.
NOTE
I think the generic version is being removed in some future version of the Web API.
In MVC 5, things got easier:
return new StatusCodeResult(HttpStatusCode.NotModified, this);
For ASP.NET Web Api 2, this post from MS suggests to change the method's return type to IHttpActionResult. You can then return a built in IHttpActionResult implementation like Ok, BadRequest, etc (see here) or return your own implementation.
For your code, it could be done like:
public IHttpActionResult GetUser(int userId, DateTime lastModifiedAtClient)
{
var user = new DataEntities().Users.First(p => p.Id == userId);
if (user.LastModified <= lastModifiedAtClient)
{
return StatusCode(HttpStatusCode.NotModified);
}
return Ok(user);
}
I hate bumping old articles but this is the first result for this in google search and I had a heck of a time with this problem (even with the support of you guys). So here goes nothing...
Hopefully my solution will help those that also was confused.
namespace MyApplication.WebAPI.Controllers
{
public class BaseController : ApiController
{
public T SendResponse<T>(T response, HttpStatusCode statusCode = HttpStatusCode.OK)
{
if (statusCode != HttpStatusCode.OK)
{
// leave it up to microsoft to make this way more complicated than it needs to be
// seriously i used to be able to just set the status and leave it at that but nooo... now
// i need to throw an exception
var badResponse =
new HttpResponseMessage(statusCode)
{
Content = new StringContent(JsonConvert.SerializeObject(response), Encoding.UTF8, "application/json")
};
throw new HttpResponseException(badResponse);
}
return response;
}
}
}
and then just inherit from the BaseController
[RoutePrefix("api/devicemanagement")]
public class DeviceManagementController : BaseController
{...
and then using it
[HttpGet]
[Route("device/search/{property}/{value}")]
public SearchForDeviceResponse SearchForDevice(string property, string value)
{
//todo: limit search property here?
var response = new SearchForDeviceResponse();
var results = _deviceManagementBusiness.SearchForDevices(property, value);
response.Success = true;
response.Data = results;
var statusCode = results == null || !results.Any() ? HttpStatusCode.NoContent : HttpStatusCode.OK;
return SendResponse(response, statusCode);
}
.net core 2.2 returning 304 status code. This is using an ApiController.
[HttpGet]
public ActionResult<YOUROBJECT> Get()
{
return StatusCode(304);
}
Optionally you can return an object with the response
[HttpGet]
public ActionResult<YOUROBJECT> Get()
{
return StatusCode(304, YOUROBJECT);
}
I don't like having to change my signature to use the HttpCreateResponse type, so I came up with a little bit of an extended solution to hide that.
public class HttpActionResult : IHttpActionResult
{
public HttpActionResult(HttpRequestMessage request) : this(request, HttpStatusCode.OK)
{
}
public HttpActionResult(HttpRequestMessage request, HttpStatusCode code) : this(request, code, null)
{
}
public HttpActionResult(HttpRequestMessage request, HttpStatusCode code, object result)
{
Request = request;
Code = code;
Result = result;
}
public HttpRequestMessage Request { get; }
public HttpStatusCode Code { get; }
public object Result { get; }
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
return Task.FromResult(Request.CreateResponse(Code, Result));
}
}
You can then add a method to your ApiController (or better your base controller) like this:
protected IHttpActionResult CustomResult(HttpStatusCode code, object data)
{
// Request here is the property on the controller.
return new HttpActionResult(Request, code, data);
}
Then you can return it just like any of the built in methods:
[HttpPost]
public IHttpActionResult Post(Model model)
{
return model.Id == 1 ?
Ok() :
CustomResult(HttpStatusCode.NotAcceptable, new {
data = model,
error = "The ID needs to be 1."
});
}
Try this :
return new ContentResult() {
StatusCode = 404,
Content = "Not found"
};
If you need to return an IHttpActionResult and want to return the error code plus a message, use:
return ResponseMessage(Request.CreateErrorResponse(HttpStatusCode.NotModified, "Error message here"));
Another option:
return new NotModified();
public class NotModified : IHttpActionResult
{
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = new HttpResponseMessage(HttpStatusCode.NotModified);
return Task.FromResult(response);
}
}
public HttpResponseMessage Post(Article article)
{
HttpResponseMessage response = Request.CreateResponse<Article>(HttpStatusCode.Created, article);
string uriToTheCreatedItem = Url.Route(null, new { id = article.Id });
response.Headers.Location = new Uri(Request.RequestUri, uriToTheCreatedItem);
return response;
}
An update to #Aliostads answer using the more moden IHttpActionResult introduced in Web API 2.
https://learn.microsoft.com/en-us/aspnet/web-api/overview/getting-started-with-aspnet-web-api/action-results#ihttpactionresult
public class TryController : ApiController
{
public IHttpActionResult GetUser(int userId, DateTime lastModifiedAtClient)
{
var user = new DataEntities().Users.First(p => p.Id == userId);
if (user.LastModified <= lastModifiedAtClient)
{
return StatusCode(HttpStatusCode.NotModified);
// If you would like to return a Http Status code with any object instead:
// return Content(HttpStatusCode.InternalServerError, "My Message");
}
return Ok(user);
}
}
I know there are several good answers here but this is what I needed so I figured I'd add this code in case anyone else needs to return whatever status code and response body they wanted in 4.7.x with webAPI.
public class DuplicateResponseResult<TResponse> : IHttpActionResult
{
private TResponse _response;
private HttpStatusCode _statusCode;
private HttpRequestMessage _httpRequestMessage;
public DuplicateResponseResult(HttpRequestMessage httpRequestMessage, TResponse response, HttpStatusCode statusCode)
{
_httpRequestMessage = httpRequestMessage;
_response = response;
_statusCode = statusCode;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = new HttpResponseMessage(_statusCode);
return Task.FromResult(_httpRequestMessage.CreateResponse(_statusCode, _response));
}
}