Pretty straightforward question. I want to be able to delete multiple IDs, just like the example below.
public async Task<ActionResult> Delete(List<int> id)`
Snippet
[HttpDelete("{id:int}")]
public async Task<ActionResult> Delete(int id)
{
await Mediator.Send(new DeleteRoomCommand { Id = id }).ConfigureAwait(false);
return NoContent();
}
public class DeleteRoomCommand : IRequest
{
public long Id { get; set; }
}
public class DeleteRoomCommandHandler : IRequestHandler<DeleteRoomCommand>
{
private readonly IApplicationDbContext _context;
public DeleteRoomCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<Unit> Handle(DeleteRoomCommand request, CancellationToken cancellationToken)
{
var entity = await _context.Rooms.FindAsync(request.Id).ConfigureAwait(false);
if (entity == null)
{
throw new NotFoundException(nameof(Room), request.Id);
}
_context.Rooms.Remove(entity);
await _context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
return Unit.Value;
}
}
You seem to be asking two questions:
How do I write a new command and handler for MediatR that will accept and pass an enumerable of IDs instead of just one?
How do I write a DbContext call to handle that enumerable?
The IRequest object is just a simple class that contains the data you need to process it. You can create a new pluralized command class that accepts an enumerable of IDs, and a new handler designed to handle that case. You'll need to tweak this based on your own testing.
public class DeleteRoomsCommand : IRequest
{
public IEnumerable<long> Ids { get; set; }
}
public class DeleteRoomsCommandHandler : IRequestHandler<DeleteRoomsCommand>
{
private readonly IApplicationDbContext _context;
public DeleteRoomCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<Unit> Handle(DeleteRoomsCommand request, CancellationToken cancellationToken)
{
var entities = await _context.Rooms.Where(r => request.Ids.Contains(r.Id)); // .ConfigureAwait(false);
_context.Rooms.RemoveRange(entities);
await _context.SaveChangesAsync(cancellationToken); // .ConfigureAwait(false);
return Unit.Value;
}
}
Your new call is instantiated the same, taking the IDs received by your controller and assigning them to a new MediatR command:
await Mediator.Send(new DeleteRoomsCommand { Ids = ids }); //.ConfigureAwait(false);
Your new method could accept the list of IDs via query parameter or via the body, depending on your need or convention.
Related
So, I have for example this Laravel Resource Controller code like this:
class BaseAPIController extends Controller
{
public function index()
{
return self::$model->all();
}
}
So, I was trying to do like that in ASP.NET C#:
[ApiController]
public class BaseAPIController<T> : ControllerBase
{
[HttpGet]
public ActionResult<IEnumerable<T>> Get()
{
using (ExamRTContext db = new ExamRTContext())
{
return db.${typeof(T).Name}.Select(x => x);
}
}
}
But I don't have any idea how to do like that.
So, Let say I just wanted to do simple CRUD in 3 tables. All operation is same, for example Get() is used to get all data from that model.
Instead of writing it 3 times, I wanted to just write it once and extend it to each model controller.
Any idea how to do that?
C# does not allow you to compose expressions at runtime like that.
However, EF has an API to do this.
You're looking for .Set<T>().
If you want to perform simple CRUD operations with entity framework you could create a generic repository.
Repository:
public class GenericRepository<TEntity, TContext>
where TContext : DbContext
where TEntity : class
{
protected readonly TContext context;
public GenericRepository(TContext context)
{
this.context = context;
}
public virtual async Task Add(TEntity model)
{
await context.Set<TEntity>().AddAsync(model);
await context.SaveChangesAsync();
}
public virtual async Task<TEntity> Get(int id)
{
return await context.Set<TEntity>().FindAsync(id);
}
public virtual async Task<IEnumerable<TEntity>> GetAll()
{
return await context.Set<TEntity>().ToListAsync();
}
public virtual async Task<TEntity> FindFirstBy(Func<TEntity,bool> predicate)
{
return await Task.Run(()=> context.Set<TEntity>().FirstOrDefault(predicate));
}
public virtual async Task<IEnumerable<TEntity>> FilterBy(Func<TEntity,bool> predicate)
{
return await Task.Run(()=> context.Set<TEntity>().Where(predicate).ToList());
}
public virtual async Task Update()
{
await context.SaveChangesAsync();
}
public virtual async Task Remove(TEntity model)
{
context.Set<TEntity>().Remove(model);
await context.SaveChangesAsync();
}
}
To be able to use it you just have to inject it in the controller specifying the Entity Type and the Context. In your example it would be like:
Controller Base:
[ApiController]
public class BaseAPIController<T> : ControllerBase
{
protected readonly GenericReposoitory<T,ExamRTContext> repository;
public BaseAPIController(GenericRepository<T,ExamRTContext> repository) {
this.repository = repository;
}
[HttpGet]
public ActionResult<IEnumerable<T>> Get()
{
var entities = repository.GetAll();
if (entities!= null) {
return Ok(entities);
}
return NotFound();
}
}
In Startup:
services.AddTransient(typeof(GenericRepository<,>), typeof(GenericRepository<,>));
So I've recently started to learn about using the MediatR library with ASP.NET Core Web API and I'm unsure how to go about returning a NotFound() when a DELETE/PUT/PATCH request has been made for an unexisting resource.
If we take DELETE for example, here is my controller action:
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
await Mediator.Send(new DeleteCourseCommand {Id = id});
return NoContent();
}
The Command:
public class DeleteCourseCommand : IRequest
{
public int Id { get; set; }
}
The Command Handler:
public class DeleteCourseCommandHandler : IRequestHandler<DeleteCourseCommand>
{
private readonly UniversityDbContext _context;
public DeleteCourseCommandHandler(UniversityDbContext context)
{
_context = context;
}
public async Task<Unit> Handle(DeleteCourseCommand request, CancellationToken cancellationToken)
{
var course = await _context.Courses.FirstOrDefaultAsync(c => c.Id == request.Id, cancellationToken);
if (course != null)
{
_context.Courses.Remove(course);
var saveResult = await _context.SaveChangesAsync(cancellationToken);
if (saveResult <= 0)
{
throw new DeleteFailureException(nameof(course), request.Id, "Database save was not successful.");
}
}
return Unit.Value;
}
}
As you can see in the Handle method, if there is an error when saving, an exception is thrown which results in a 500 internal server error (which is correct I believe). But if the Course is not found, how can I feed this back to the Action on the Controller? Is it simply a case of invoking a Query to GET the course in the Controller Action, then return NotFound() if it doesn't exist or then invoke the Command to DELETE the Course? This would work of course but of all the examples I've been through, I haven't come across an Action which uses two Mediator calls.
MediatR supports a Request/Response pattern, which allows you to return a response from your handler class. To use this approach, you can use the generic version of IRequest, like this:
public class DeleteCourseCommand : IRequest<bool>
...
In this case, we're stating that bool will be the response type. I'm using bool here for simplicity: I'd suggest using something more descriptive for your final implementation but bool suffices for explanation purposes.
Next, you can update your DeleteCourseCommandHandler to use this new response type, like this:
public class DeleteCourseCommandHandler : IRequestHandler<DeleteCourseCommand, bool>
{
...
public async Task<bool> Handle(DeleteCourseCommand request, CancellationToken cancellationToken)
{
var course = ...
if (course == null)
return false; // Simple example, where false means it wasn't found.
...
return true;
}
}
The IRequestHandler being implemented now has two generic types, the command and the response. This requires updating the signature of Handle to return a bool instead of Unit (in your question, Unit isn't being used).
Finally, you'll need to update your Delete action to use the new response type, like this:
public async Task<IActionResult> Delete(int id)
{
var courseWasFound = await Mediator.Send(new DeleteCourseCommand {Id = id});
if (!courseWasFound)
return NotFound();
return NoContent();
}
I like returning events from my commands. The command is telling your application what the client wants it to do. The response is what it actually did.
BTW—it's said that command handlers should return anything. That's really only true in a fully async environment where the command won't be completed until sometime after the response to the client that it's accepted. In that case, you would return Task<Unit> and publish these events. The client would get them via some other channel, like a SignalR hub once they were raised. Either way, events are the best way to tell a client what's going on in your application.
Start by defining an interface for your events
public interface IEvent
{
}
Then, create events for each of the things that can happen in a command. You can include information in them if you'd want to do something with that information or just leave them empty if the class itself is enough.
public class CourseNotFoundEvent : IEvent
{
}
public class CourseDeletedEvent : IEvent
{
}
Now, have your command return an event interface.
public class DeleteCourseCommand : IRequest<IEvent>
{
}
Your handler would look something like this:
public class DeleteCourseCommandHandler : IRequestHandler<DeleteCourseCommand, IEvent>
{
private readonly UniversityDbContext _context;
public DeleteCourseCommandHandler(UniversityDbContext context)
{
_context = context;
}
public async Task<IEvent> Handle(DeleteCourseCommand request, CancellationToken cancellationToken)
{
var course = await _context.Courses.FirstOrDefaultAsync(c => c.Id == request.Id, cancellationToken);
if (course is null)
return new CourseNotFoundEvent();
_context.Courses.Remove(course);
var saveResult = await _context.SaveChangesAsync(cancellationToken);
if (saveResult <= 0)
{
throw new DeleteFailureException(nameof(course), request.Id, "Database save was not successful.");
}
return new CourseDeletedEvent();
}
}
Finally, you can use pattern matching on your web API to do things based on the event that gets returned.
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
var #event = await Mediator.Send(new DeleteCourseCommand {Id = id});
if(#event is CourseNotFoundEvent)
return NotFound();
return NoContent();
}
I managed to solve my problem through some more examples I found. The solution is to define custom Exceptions such as NotFoundException and then throw this in the Handle method of the Query/Command Handler. Then in order for MVC to handle this appropriately, an implementation of ExceptionFilterAttribute is needed to decide how each Exception is handled:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
if (context.Exception is ValidationException)
{
context.HttpContext.Response.ContentType = "application/json";
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
context.Result = new JsonResult(
((ValidationException)context.Exception).Failures);
return;
}
var code = HttpStatusCode.InternalServerError;
if (context.Exception is NotFoundException)
{
code = HttpStatusCode.NotFound;
}
context.HttpContext.Response.ContentType = "application/json";
context.HttpContext.Response.StatusCode = (int)code;
context.Result = new JsonResult(new
{
error = new[] { context.Exception.Message }
});
}
}
Startup Class:
services.AddMvc(options => options.Filters.Add(typeof(CustomExceptionFilterAttribute)));
Custom Exception:
public class NotFoundException : Exception
{
public NotFoundException(string entityName, int key)
: base($"Entity {entityName} with primary key {key} was not found.")
{
}
}
Then in the Handle method:
if (course != null)
{
_context.Courses.Remove(course);
var saveResult = await _context.SaveChangesAsync(cancellationToken);
if (saveResult <= 0)
{
throw new DeleteFailureException(nameof(course), request.Id, "Database save was not successful.");
}
}
else
{
throw new NotFoundException(nameof(Course), request.Id);
}
return Unit.Value;
This seems to do the trick, if anyone can see any potential issues with this please let me know!
I have a simple web-app with angular on client-side and asp.net core web-api on server-side. I use InMemoryDatabase
services.AddDbContext<ItemsContext>(options => options.UseInMemoryDatabase("ItemsDB"));
to store data for the simplisity of the development. But I've encountered an issue with that. I have one controller on web-api to response for users' requests:
[Route("api/[controller]")]
public class ItemsController : Controller
{
private readonly IApiService apiService;
public ItemsController(IApiService apiService)//using DI from Startup.cs
{
this.apiService = apiService;
}
[HttpPost, Route("addItem")]
public async Task<Response> Add([FromBody]Item item)
{
return await apiService.Add(item);
}
[HttpDelete("{id}")]
public async Task<Response> Delete(int id)
{
return await apiService.Delete(id);
}
[HttpPut]
public async Task<Response> Put([FromBody]Item item)
{
return await apiService.Put(item);
}
}
and the following Startup.cs configurations:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDbContext<ItemsContext>(options => options.UseInMemoryDatabase("ItemsDB"));
services.AddSingleton<IUnitOfWork, UnitOfWork>(provider => {
var context = services.BuildServiceProvider().GetService<ItemsContext>();
return new UnitOfWork(context);
});
services.AddSingleton<IApiService, ApiService>(provider => {
return new ApiService(services);
});
}
The problem is, that when I add new item, everything goes just fine...but then I post another request to delete this item it may show there there is no such an item at all or sometimes it may delete it...so in other words, the database exists and then disappears and I'm not sure when. Here is some additional code refering to the above
public class ApiService: IApiService
{
private readonly IUnitOfWork database;
private readonly IServiceProvider provider;
public ApiService(IServiceCollection serviceCollection)
{
provider = serviceCollection.BuildServiceProvider();
}
public IUnitOfWork Database
{
get
{
return provider.GetService<IUnitOfWork>();
}
}
public async Task<Response> Add(Item item)
{
Database.Items.Add(item);
await Database.SaveAsync();
var id = Database.Items.LastItem().Id;
return new Response() { Result = true, ItemId = id };
}
public async Task<Response> Delete(int id)
{
var item = await db.Items.Find(id);
Database.Items.Remove(item);
await Database.SaveAsync();
return new Response() { Result = true };
}
public async Task<Response> Put(Item item)
{
Database.Items.Update(item);
await Database.SaveAsync();
return new Response() { Result = true };
}
}
Update:
UnitOfWork Implementation:
public class UnitOfWork: IUnitOfWork
{
private readonly DbContext context;
private IRepository<Item> itemsRepository;
public UnitOfWork(DbContext dbContext)
{
context = dbContext;
}
public IRepository<Item> Items
{
get
{
return itemsRepository ?? (itemsRepository = new Repository<Item>(context));
}
}
public void Dispose()
{
context.Dispose();
}
public void Save()
{
context.SaveChanges();
}
public async Task SaveAsync()
{
await context.SaveChangesAsync();
}
}
Your code has multiple serious problems, let's go through them.
services.AddDbContext adds a Scoped service, meaning that instances will be created and disposed on each request. services.AddSingleton adds a Singleton service, so only a single instance will ever be created. You cannot add a scoped service to a singleton one, because the reference the singleton service uses will be disposed and you will end up with a disposed context.
This code:
return provider.GetService<IUnitOfWork>();
represents the service locator anti-pattern. As you can guess, an anti-pattern is something you want to avoid. I also don't know why you would want a service to build the entire DI container nor why you would want a service to have the responsibility of getting the dependencies it needs itself.
This part here is where your question actually comes from:
Database.SaveAsync();
You are calling an asynchronous function and not awaiting for it to finish. The task may finish or not, it may throw an error or not, you will never know what happened.
The best thing is that all of these could be avoided if people stopped attempting to create a Unit of Work + Repository pattern over yet another Unit of Work and Repository. Entity Framework Core already implements these:
DbContext => Unit of Work
DbSet => Repository (generic)
Why do you want yet another abstraction? Will you really ever throw away EF Core from the project to justify the maintenance cost of your code?
The entire question code could have just been this:
[Route("api/[controller]")]
public class ItemsController : Controller
{
private readonly YourContext _context;
public ItemsController(YourContext context)
{
_context = context;
}
[HttpPost]
public async Task<IActionResult> Add([FromBody]Item item)
{
context.Items.Add(item);
await context.SaveChangesAsync();
return Ok(item.Id);
}
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
var item = await context.Items.FindAsync(id);
context.Items.Remove(item);
await context.SaveChangesAsync();
return Ok();
}
[HttpPut]
public async Task<IActionResult> Put([FromBody]Item item)
{
context.Items.Update(item);
await context.SaveChangesAsync();
return Ok();
}
}
I am building an ASP.NET Core API. I have an action that I want to be essentially identical across a set of controllers. So, I created the EntityController that those controllers inherit from as below.
Note: The ellipsis used in both classes below represent many more actions and their related services following the same pattern omitted for simplicity.
public class EntityController : BaseController
{
protected readonly SeedService SeedService;
protected EntityController(IMemoryCache memoryCache, SeedService seedService) : base(memoryCache)
{
SeedService = seedService;
}
[HttpGet]
public async Task<IActionResult> Seed()
{
var controllerName = ControllerContext.RouteData.Values["controller"].ToString();
return await GetSeed(controllerName);
}
private async Task<IActionResult> GetSeed(string controllerName)
{
switch (controllerName)
{
case "lists":
return await MemoryCache.GetOrCreateAsync(CacheKeys.Entry, async entry =>
{
entry.SlidingExpiration = TimeSpan.FromSeconds(3);
return Json(await SeedService.GetAllFilterLists());
});
case "languages":
return await MemoryCache.GetOrCreateAsync(CacheKeys.Entry, async entry =>
{
entry.SlidingExpiration = TimeSpan.FromSeconds(3);
return Json(await SeedService.GetAllLanguages());
});
...
default:
return await Task.FromResult(NotFound());
}
}
}
Here are the service methods that these actions call:
public class SeedService
{
private readonly FilterListsDbContext filterListsDbContext;
public SeedService(FilterListsDbContext filterListsDbContext)
{
this.filterListsDbContext = filterListsDbContext;
}
public async Task<IEnumerable<FilterListSeedDto>> GetAllFilterLists()
{
return await filterListsDbContext.Set<FilterList>().ProjectTo<FilterListSeedDto>().ToListAsync();
}
public async Task<IEnumerable<LanguageSeedDto>> GetAllLanguages()
{
return await filterListsDbContext.Set<Language>().ProjectTo<LanguageSeedDto>().ToListAsync();
}
...
}
How can I use generics (or alternative) to reduce this copy/paste duplication? I tried using something like a Dictionary<string, Type> to lookup the Type dynamically from the controller name, but I am not sure how the resulting GetAll<T>() method in SeedService would look? Below doesn't work because the method depends on the types of both the entity and DTO models for the AutoMapper projection.
public async Task<IEnumerable<T>> GetAll<T>()
{
return await filterListsDbContext.Set<T>().ProjectTo<T>().ToListAsync();
}
You could easily remove all that boilerplate code into a single generic method:
public async Task<IEnumerable<TResult>> GetAll<TEntry, TResult>() where TEntry : class
{
return await filterListsDbContext.Set<TEntry>()
.ProjectTo<TResult>()
.ToListAsync();
}
Since you are returning an IEnumerable, you may want to change to .ToArrayAsync(). Also, since you are projecting to non-entities, and hence changes won't be picked up by the context, you could go further and add .AsNoTracking() to avoid adding the entities to the context:
public async Task<IEnumerable<TResult>> GetAll<TEntry, TResult>() where TEntry : class
{
return await filterListsDbContext.Set<TEntry>()
.AsNoTracking()
.ProjectTo<TResult>()
.ToArrayAsync();
}
As I mentioned in the comments, you could put that in a base controller and do something like this:
public class BaseController<TEntity, TViewModel>
{
public async Task<IEnumerable<TViewModel>> GetAll()
{
return await filterListsDbContext.Set<TEntity>()
.AsNoTracking()
.ProjectTo<TViewModel>()
.ToArrayAsync();
}
}
public class LanguageController : BaseController<Language, LanguageSeedDto>
{
(in some action)
var data = await GetAll();
}
I have a WebApi controller which calls a third party API in asynchronous mode.
All works ok and now I want to sort the result in a separate action method.
Now, when I call the API, the callback with the result never happens after running "await client.GetAsycn(...)" in the DAL. What am I missing?
This is my API controller:
// GET api/lookup
[ResponseType(typeof(RestaurantModel))]
public async Task<IHttpActionResult> Get(string outcode)
{
if (string.IsNullOrEmpty(outcode)) throw new ArgumentNullException(nameof(outcode));
var result = await _repository.GetRestaurantsByOutcode(outcode);
return Ok(new RestaurantModel()
{
Result = result
});
}
// GET api/sorted
[System.Web.Http.Route("~/api/sorted")]
public List<Restaurant> GetSorted(string outcode)
{
if (string.IsNullOrEmpty(outcode)) throw new ArgumentNullException(nameof(outcode));
return _repository.GetSortedRestaurantsByOutcode(outcode);
}
This is my repository with a new method to sort the result:
public class RestaurantRepository : IRestaurantRepository
{
private readonly IContext _context;
public RestaurantRepository(IContext context)
{
_context = context;
}
public Task<ApiResult> GetRestaurantsByOutcode(string outcode)
{
return _context.GetRestaurantsByOutcode(outcode);
}
public List<Restaurant> GetSortedRestaurantsByOutcode(string outcode)
{
return _context.GetRestaurantsByOutcode(outcode).Result.Restaurants
.OrderBy(x => x.Name).ToList();
}
}
This is my DAL to call the third party API:
public async Task<ApiResult> GetRestaurantsByOutcode(string outcode)
{
using (var client = new HttpClient())
{
ConfigureHttpClient(client);
var response = await client.GetAsync(
$"restaurants?q={WebUtility.UrlEncode(outcode)}");
return response.IsSuccessStatusCode
? await response.Content.ReadAsAsync<ApiResult>()
: null;
}
}
You have a mix-match of sometimes you use async/await and other times you don't. Async / await (can and does by default) ensures that the call resumes on the calling thread so the context is resulted. You need to allign your code so you make use of the async/await in the whole stack. Otherwise you are creating a deadlock for your self.
[System.Web.Http.Route("~/api/sorted")]
// missing async in signature (not good if you are calling it with await in your controller)
public async Task<List<Restaurant>> GetSorted(string outcode)
{
if (string.IsNullOrEmpty(outcode)) throw new ArgumentNullException(nameof(outcode));
// added await in call
return await _repository.GetSortedRestaurantsByOutcode(outcode);
}
DAL
public class RestaurantRepository : IRestaurantRepository
{
private readonly IContext _context;
public RestaurantRepository(IContext context)
{
_context = context;
}
// added async and await
public async Task<ApiResult> GetRestaurantsByOutcode(string outcode)
{
return await _context.GetRestaurantsByOutcode(outcode);
}
// added async and await
public async Task<List<Restaurant>> GetSortedRestaurantsByOutcode(string outcode)
{
// here you were not using await but then using result even though you were calling into a method marked as async which in turn used an await. this is where you deadlocked but this the fix.
return (await _context.GetRestaurantsByOutcode(outcode)).Restaurants
.OrderBy(x => x.Name).ToList();
}
}