How is Mediatr wired? - c#

Update:
The Mediatr in the project is used without any customized logic for dispatching the messages. Can I say it's used as an event aggregator?
In the source code of https://github.com/JasonGT/NorthwindTraders, the Controller gets the Mediator from ControllerBase.
[ApiController]
[Route("api/[controller]/[action]")]
public abstract class BaseController : ControllerBase
{
private IMediator _mediator;
protected IMediator Mediator => _mediator ??= HttpContext.RequestServices.GetService<IMediator>();
}
In the controller, it calls Mediator.Send(...) to send the message to the mediator.
public class EmployeesController : BaseController
{
// ....
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<EmployeeDetailVm>> Get(int id)
{
return Ok(await Mediator.Send(new GetEmployeeDetailQuery { Id = id }));
}
And the method Handle() in the inner class GetEmployeeDetailQuery.GetEmployeeDetailQueryHandler will be called for query message GetEmployeeDetailQuery. How is this wired?
public class GetEmployeeDetailQuery : IRequest<EmployeeDetailVm>
{
public int Id { get; set; }
public class GetEmployeeDetailQueryHandler : IRequestHandler<GetEmployeeDetailQuery, EmployeeDetailVm>
{
private readonly INorthwindDbContext _context;
private readonly IMapper _mapper;
public GetEmployeeDetailQueryHandler(INorthwindDbContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
public async Task<EmployeeDetailVm> Handle(GetEmployeeDetailQuery request, CancellationToken cancellationToken)
{
var vm = await _context.Employees
.Where(e => e.EmployeeId == request.Id)
.ProjectTo<EmployeeDetailVm>(_mapper.ConfigurationProvider)
.SingleOrDefaultAsync(cancellationToken);
return vm;
}
}
}

In the startup.cs of that project, there's a call to AddApplication, which is an extension method from the NorthwindTraders.Application project, and is defined in DependencyInjection.cs. This calls services.AddMediatR(Assembly.GetExecutingAssembly());, which scans the assembly for handlers and registers them.
In general, you can register MediatR for your own projects by calling services.AddMediatr(Assembly.GetExecutingAssembly()) in your web application's Startup.ConfigureServices method.

Related

Cannot access a disposed object in Task.Run

I am using .NET Core 3.1. I want to run some background processing without user having to wait for it to finish (it takes about 1 minute). Therefore, I used Task.Run like this:
public class MyController : Controller
{
private readonly IMyService _myService;
public MyController(IMyService myService)
{
_myService = myService;
}
public async Task<IActionResult> Create(...)
{
await _myService.CreatePostAsync(...);
return View();
}
}
public class MyService : IMyService
{
private readonly MyDbContext _dbContext;
private readonly IServiceScopeFactory _scopeFactory;
public MyService(MyDbContext dbContext, IServiceScopeFactory scopeFactory)
{
_dbContext = dbContext;
_scopeFactory = scopeFactory;
}
public async Task CreatePostAsync(Post post)
{
...
string username = GetUsername();
DbContextOptions<MyDbContext> dbOptions = GetDbOptions();
Task.Run(() => SaveFiles(username, dbOptions, _scopeFactory));
}
private void SaveFiles(string username, DbContextOptions<MyDbContext> dbOptions, IServiceScopeFactory scopeFactory)
{
using (var scope = scopeFactory.CreateScope())
{
var otherService = scope.ServiceProvider.GetRequiredService<IOtherService>();
var cntxt = new MyDbContext(dbOptions, username);
Post post = new Post("abc", username);
cntxt.Post.Add(post); <----- EXCEPTION
cntxt.SaveChanges();
}
}
}
I recieve the following exception in marked line:
System.ObjectDisposedException: 'Cannot access a disposed object. Object name: 'IServiceProvider'.'
Why does this happen? I used custom constructor (and not scope.ServiceProvider.GetRequiredService<MyDbContext>()) for MyDbContext because I need to save one additional propery (username) for later use in overriden methods.
public partial class MyDbContext
{
private string _username;
private readonly DbContextOptions<MyDbContext> _options;
public DbContextOptions<MyDbContext> DbOptions { get { return _options; } }
public MyDbContext(DbContextOptions<MyDbContext> options, string username) : base(options)
{
_username = username;
_options = options;
}
... other overriden methods
}
What am I doing wrong?
First of all, don't hide a thread-pool operation away in your service; let the calling coded decide whether to run the operation on the thread-pool or not:
As you are using dependency injection, the framework is disposing your DbContext at the end of the HTTP request.
You need to inject your service scope factory into your controller, and request the service from there:
public class MyController : Controller
{
private readonly IMyService _myService;
private readonly IServiceScopeFactory _scopeFactory;
public MyController(IMyService myService, IServiceScopeFactory scopeFactory)
{
_myService = myService;
_scopeFactory = scopeFactory;
}
public async Task<IActionResult> Create(...)
{
HostingEnvironment.QueueBackgroundWorkItem(SaveInBackground);
return View();
}
private async Task SaveInBackground(CancellationToken ct)
{
using (var scope = scopeFactory.CreateScope())
{
var scopedService = scope.ServiceProvider.GetRequiredService<IMyService>();
await scopedService.CreatePostAsync(...);
}
}
}
HostingEnvironment.QueueBackgroundWorkItem works in a similar way to Task.Run, except it ensures that the app doesn't shut down until all background work items have completed.
Your service would need to be something like this:
public class MyService : IMyService
{
private readonly MyDbContext _dbContext;
public MyService(MyDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task CreatePostAsync(Post post)
{
_dbContext.Post.Add(post);
await _dbContext.SaveChangesAsync();
}
}
UPDATE
To pass additional parameters to SaveInBackground:
private async Task SaveInBackground(YourParam param)
Then call like:
HostingEnvironment.QueueBackgroundWorkItem(cancellationToken => SaveInBackground(yourParam));
You shoud create a Service with a Singleton lifecycle and inject a DBContext inside and queue all tasks inside

How save globally catched exceptions in database

I am building a web application where I will have a lot of controllers with their corresponding action methods in them.
I want to save every exception in database and for this reason I have created
ExceptionService (DbContext is injected in it).
let's say that this is the general form of my controllers:
[Route("api/[controller]")]
[ApiController]
public class UserController : ControllerBase
{
private readonly UserManager userManager;
private readonly IExceptionService exceptionService;
public UserController(UserManager userManager, IExceptionService exceptionService)
{
this.userManager = userManager;
this.exceptionService = exceptionService;
}
[HttpPost]
public async Task<IActionResult> Post([FromBody] User user)
{
try
{
//some code
}
catch (Exception e)
{
exceptionService.Save(e);
//some code
}
}
}
In order to avoid so many try-catch blocks I decided to create a filter which looks like this:
public class ApiExceptionFilterAttribute : ExceptionFilterAttribute
{
private readonly IExceptionService exceptionService;
public ApiExceptionFilterAttribute(IExceptionService exceptionService)
{
this.exceptionService = exceptionService;
}
public override void OnException(ExceptionContext context)
{
Exception e = context.Exception;
exceptionService.Save(e);
//some code
}
}
Code in ConfigureServices method in StartUp.cs looks like this (some code removed for simplicity):
services
.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddJsonOptions(options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
services
.AddDbContext<AppDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Default")));
services.AddScoped<UserManager>();
services.AddScoped<SignInManager>();
services.AddScoped<IExceptionService, ExceptionService>();
services.AddSingleton<IConfigureOptions<MvcOptions>, ConfigureMvcOptions>();
ConfgureMvcOptions class looks like this:
public class ConfigureMvcOptions : IConfigureOptions<MvcOptions>
{
private readonly IExceptionService exceptionService;
public ConfigureMvcOptions(IExceptionService exceptionService)
{
this.exceptionService = exceptionService;
}
public void Configure(MvcOptions options)
{
options.Filters.Add(new ApiExceptionFilterAttribute(exceptionService));
}
}
When I run this application, I get the following error:
System.InvalidOperationException: 'Cannot consume scoped service 'SmartWay.Services.IExceptionService' from singleton 'Microsoft.Extensions.Options.IConfigureOptions`1[Microsoft.AspNetCore.Mvc.MvcOptions]'.'
If I change IExceptionServcise's lifetime to transient than I have to do so for
Dbcontext, then for DbContextOptions... It seems that it isn't right way..
So, How can I solve this problem?
For resolving scoped service from singleton service, try _serviceProvider.CreateScope.
Follow steps below:
ExceptionService
public interface IExceptionService
{
void Save(Exception ex);
}
public class ExceptionService : IExceptionService
{
private readonly IServiceProvider _serviceProvider;
public ExceptionService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void Save(Exception ex)
{
using (var scope = _serviceProvider.CreateScope())
{
var _context = scope.ServiceProvider.GetRequiredService<MVCProContext>();
_context.Add(new Book() { Title = ex.Message });
_context.SaveChanges();
}
}
}
Startup.cs
services.AddSingleton<IExceptionService, ExceptionService>();
services.AddSingleton<IConfigureOptions<MvcOptions>, ConfigureMvcOptions>();

Add attribute to inherited function c#

I am working on a webapi application on .net Core and I have a base controller from which all other controller derives from.
Here is the class:
public class ReadOnlyBaseController<TEntity, TEntityResource> : Controller
{
private readonly IMapper mapper;
private readonly IBaseUnitOfWork unitOfWork;
private readonly IBaseRepository<TEntity> repository;
public ReadOnlyBaseController(IBaseRepository<TEntity> repository, IBaseUnitOfWork unitOfWork,
IMapper mapper)
{
this.repository = repository;
this.unitOfWork = unitOfWork;
this.mapper = mapper;
}
[HttpGet]
[Authorize]
virtual public async Task<IActionResult> Get()
{
List<TEntity> TEntitys = await this.repository.GetTodos();
return Ok(TEntitys);
}
[HttpGet("Id")]
[Authorize]
virtual public IActionResult GeSingle(int Id)
{
TEntity tEntity = this.repository.GetSingle(Id);
TEntityResource tEntityResource = this.mapper.Map<TEntity, TEntityResource>(tEntity);
return Ok(tEntityResource);
}
}
However, some of my API endpoints do not require the Authorize attribute. So I created another base controller:
public class ReadOnlyNoOAuthBaseController<TEntity, TEntityResource> : Controller
{
private readonly IMapper mapper;
private readonly IBaseUnitOfWork unitOfWork;
private readonly IBaseRepository<TEntity> repository;
public ReadOnlyNoOAuthBaseController(IBaseRepository<TEntity> repository, IBaseUnitOfWork unitOfWork,
IMapper mapper)
{
this.repository = repository;
this.unitOfWork = unitOfWork;
this.mapper = mapper;
}
[HttpGet]
virtual public async Task<IActionResult> Get()
{
List<TEntity> TEntitys = await this.repository.GetTodos();
return Ok(TEntitys);
}
[HttpGet("Id")]
virtual public IActionResult GeSingle(int Id)
{
TEntity tEntity = this.repository.GetSingle(Id);
TEntityResource tEntityResource = this.mapper.Map<TEntity, TEntityResource>(tEntity);
return Ok(tEntityResource);
}
}
As you probably noticed, other than the [Authorize] attribute, the controllers are identical. Is there any way to make this work without the need to create a new controller?
Cheers!
Create the base controller with no authorizations attributes.
public class ReadOnlyBaseController<TEntity, TEntityResource> : Controller {
protected readonly IMapper mapper;
protected readonly IBaseUnitOfWork unitOfWork;
protected readonly IBaseRepository<TEntity> repository;
public ReadOnlyBaseController(
IBaseRepository<TEntity> repository, IBaseUnitOfWork unitOfWork, IMapper mapper) {
//...
}
[HttpGet]
public virtual async Task<IActionResult> Get() {
//..
}
[HttpGet("Id")]
public virtual IActionResult GeSingle(int Id) {
//...
}
}
Then in derived controllers that need auth you can add it on the controller itself
[Authorize]
public class ReadOnlyOAuthController<TEntity, TEntityResource> : ReadOnlyBaseController<TEntity, TEntityResource> {
public ReadOnlyOAuthController(
IBaseRepository<TEntity> repository, IBaseUnitOfWork unitOfWork, IMapper mapper)
: base(repository, unitOfWork, mapper) {
}
[AllowAnonymous]
[HttpGet("someaction")]
public IAction SomeOtherAction() {
//...
}
}
the [Authorize] attribute will apply to all actions on the derived controller and if you want to allow an action you can use the [AllowAnonymous] attribute.

context.SaveChanges() not updating data in database

My ApplictionDbContextClass looks like this :-
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
//private static ApplicationDbContext _context;
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
public DbSet<Trip> Trips { get; set; }
public DbSet<Place> Places { get; set; }
public DbSet<UserTripDetail> UserTripDetails { get; set; }
public DbSet<TripPicture> TripPictures { get; set; }
}
My TripPictureController looks like this:-
//private readonly ApplicationDbContext _db = new ApplicationDbContext();
private readonly IUnitOfWork _unitOfWork;
private readonly ITripPictureRepository _tripPictureRepository;
public TripPicturesController(IUnitOfWork unitOfWork, ITripPictureRepository tripPictureRepository)
{
_unitOfWork = unitOfWork;
_tripPictureRepository = tripPictureRepository;
}
It also contains a post Action:-
[HttpPost]
public ActionResult Create(TripPicture model, HttpPostedFileBase ImageData)
{
if (ImageData != null)
{
model.TripId = 1;
model.Image = this.ConvertToBytes(ImageData);
}
_tripPictureRepository.Add(model);
_unitOfWork.Commit();
//_db.TripPictures.Add(model);
//_db.SaveChanges();
return View(model);
}
When ever i hit the post request, the model is not pushed into database. I am using dependency injection here. My guess is somewhere there is creation of different context object. i saw the following code in startup class :-
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(**ApplicationDbContext.Create**);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);}
My unitOfWork class is:-
public class UnitOfWork : IUnitOfWork
{
private DbContext _context;
public UnitOfWork(DbContext dbContext)
{
_context = dbContext;
}
public void Commit()
{
_context.SaveChanges();
}
}
and my repository class is :-
public class Repository<T> : IRepository<T> where T : class
{
protected DbSet<T> _dbSet;
public Repository(DbContext context)
{
_dbSet = context.Set<T>();
}
public void Add(T entity)
{
_dbSet.Add(entity);
}
}
The object is saved when i don't use unitOfWork. What is the problem!?
You are implementing Unit Of Work pattern incorrectly.
You are adding an item to _tripPictureRepository instance DbContext and calling _unitOfWork.Commit() on _unitOfWork instance which has another DbContext instance that has no idea about the added item (Item isn't tracked by the _unitOfWork's DbContext) which means it saves nothing.
The correct implementation of Unit of work is that your repositories should be exposed as properties, a DbContext injected to your Unit of work class and DbSet<T> of the repository will be populated from the DbContext like this:
public class UnitOfWork : IUnitOfWork
{
private DbContext _context;
public ITripPictureRepository TripsRepository{ get; }
public UnitOfWork(DbContext dbContext)
{
_context = dbContext;
Trips = new Repository<Trip>(_context.Trips)
}
public void Commit()
{
_context.SaveChanges();
}
}
Then in your controller inject an IUnitOfWork instance:
private readonly IUnitOfWork _unitOfWork;
public TripPicturesController(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
_tripPictureRepository = tripPictureRepository;
}
And now use the _unitOfWork instance to do your CRUD operations:
[HttpPost]
public ActionResult Create(TripPicture model, HttpPostedFileBase ImageData)
{
if (ImageData != null)
{
model.TripId = 1;
model.Image = this.ConvertToBytes(ImageData);
}
_unitOfWork.TripsRepository.Add(model);
_unitOfWork.Commit();
return View(model);
}
You can read more about Repository and Unit Of Work patter together from this Microsoft Docs page.
I found the issue. My DbContextObjects were different whenever generated by ninject. In my ninject file i was registering services in kernel as following :-
kernel.Bind<DbContext>().To<ApplicationDbContext>();
This by default uses Transient scope, i.e. new object is created every time required.
hence i needed to change that to following :-
kernel.Bind<DbContext>().To<ApplicationDbContext>().InRequestScope();
The requested scope means - Only a single instance of the type will be created, and the same instance will be returned for each subsequent request.
More can be found here :- https://github.com/ninject/ninject/wiki/Object-Scopes

There is no argument given in context

I am trying to implement the repository pattern in asp core. Everything seems to work fine with a few adjustments,except adding it to the controller:
public class HomeController : Controller
{
private IDocumentRepository _context;
public HomeController()
{
_context = new DocumentRepository(new myContext());
}
}
DocumentRepository.cs
public class DocumentRepository : IDocumentRepository, IDisposable
{
private myContext context;
public DocumentRepository(myContext context) : base()
{
this.context = context;
}
public IEnumerable<Document> GetDocuments()
{
return context.Document.ToList();
}
public Document GetDocumentByID(int id)
{
return context.Document.FirstOrDefault(x => x.Id == id);
}
IDocumentRepository.cs
public interface IDocumentRepository : IDisposable
{
IEnumerable<Document> GetDocuments();
Document GetDocumentByID(int documentId);
void InsertDocument(Document student);
void DeleteDocument(int documentID);
void UpdateDocument(Document document);
void Save();
}
The error
There is no argument given that corresponds to the required formal
parameter 'options' of
'myContext.myContext(DbContextOptions)
dotnetcore..NETCoreApp,Version=v1.0
Simply resolve IDocumentRepository from the DI container using constructor injection instead of manually instantiating it and it should work:
public class HomeController : Controller {
private IDocumentRepository _repository;
public HomeController(IDocumentRepository repository) {
_repository = repository;
}
}
For that, you'll need to ensure IDocumentRepository is correctly registered in ConfigureServices:
public void ConfigureServices(IServiceCollection services) {
services.AddScoped<IDocumentRepository, DocumentRepository>();
}

Categories