I've written an ExceptionFilter attribute which in that i need to access dbContext class to do database affairs. but i receive null reference in my filter attribute.
Is there any way that i can get working reference of dbContext?
public class AppExceptionAttribute : ExceptionFilterAttribute
{
AppIdentityDbContext _context;
public AppExceptionAttribute(AppIdentityDbContext context)
{
_context = context;
}
public AppExceptionAttribute()
{ }
public override async Task OnExceptionAsync(ExceptionContext context)
{
var exception = context.Exception;
while (exception != null)
{
//here _context is null, that is a dbContext class
_context.Errors.Add(new Entities.Error {
Message = exception.Message,
StackTrace = exception.StackTrace,
Date = DateTime.Now
});
exception = exception.InnerException;
}
await _context.SaveChangesAsync();
}
}
i need to mention that is an asp.net core application
You can access the IServiceProvider from the ExceptionContext.
public override async Task OnExceptionAsync(ExceptionContext context)
{
var db = context.HttpContext.RequestServices.GetService<AppIdentityDbContext>();
...
await db.SaveChangesAsync();
}
Related
I've created a service that's used throughout my aspnet project that retrieves and validates a header among other things. Issue is that the Exception Filter is not able to catch the errors that are thrown by the service as it's not in the scope of the Exception Filter thus giving the user an ugly internal server error. Is there any way to gracefully return an argument error with description to the user with the use of the services?
The Startup:
services.AddScoped<UserService>();
services
.AddMvc(x =>
{
x.Filters.Add(typeof(Filters.MyExceptionFilter));
})
The Exception Filter:
public class MyExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
if (context.Exception is ArgumentException argumentException)
{
var response = context.HttpContext.Response;
context.ExceptionHandled = true;
response.StatusCode = 400;
context.Result = new ObjectResult(argumentException.Message);
return;
}
}
}
The Service:
public class UserService
{
public readonly string UserId;
public UserService(IHttpContextAccessor context)
{
if (!context.HttpContext.Request.Headers.TryGetValue("x-test", out var user))
{
throw new ArgumentException($"x-test header is required.");
}
UserId = user;
//do other stuff
}
}
Controller Action Method:
public async Task<IActionResult> Delete(string id,
[FromServices] UserService userService)
{
//do stuff
}
A rule-of-thumb in C# is do as little work in the constructor as possible. There's a few good reasons for this (e.g. you can't dispose a class that threw an exception in it's constructor). Another good reason is, as you have found out, construction might happen in a different place (e.g. a DI container) than the place you actually use the class.
The fix should be quite straightforward - just move the logic out of the constructor. You could use a Lazy<T> to do this for example:
public class UserService
{
public readonly Lazy<string> _userId ;
public UserService(IHttpContextAccessor context)
{
_userId = new Lazy<string>(() =>
{
if (!context.HttpContext.Request.Headers.TryGetValue("x-test", out var user))
{
throw new ArgumentException($"x-test header is required.");
}
return user;
});
//do other stuff
}
public string UserId => _userId.Value;
}
Or you could just get the value when you needed it:
public class UserService
{
public readonly IHttpContextAccessor _context;
public UserService(IHttpContextAccessor context)
{
_context = context;
//do other stuff
}
public string UserId
{
get
{
if (_context.HttpContext.Request.Headers.TryGetValue("x-test", out var user))
{
return user;
}
else
{
throw new ArgumentException($"x-test header is required.");
}
}
}
}
I have a userRepo which holds my users data. Over night I check if the display names of the users changed. In the task scheduler class I am only using the the userRepository context in the functions below and I quite don't understand how I get a second operation exception in the SaveChangesAsync() Function.
Am I missing somethings if the context is not called over the Web-API instead its called internally from my task scheduler class?
This is the repository class
public class UserRepository
{
private RTBMContext context;
public UserRepository(RTBMContext context)
{
this.context = context;
}
public async Task<TUser> Update(TUser entity)
{
if (entity == null)
{
return null;
}
var item = await context.TUsers.FindAsync(entity.PkUserNt);
this.context.Entry(item).CurrentValues.SetValues(entity);
await this.context.SaveChangesAsync(); //Crash !!!!!!!!!!!!!!!!!!!!!
return entity;
}
}
public partial class RTBMContext : DbContext
{
public RTBMContext(DbContextOptions<RTBMContext> options)
: base(options)
{
}
...
Startup class:
public void ConfigureServices(IServiceCollection services)
{
// Register DbContext
services.AddDbContextPool<Models.RTBMContext>(options =>
{
options.UseNpgsql(this.Configuration.GetConnectionString("DBConnection"));
});
// Repository registration
services.AddScoped<UserRepository>();
services.AddScoped<TaskScheduler>();
var serviceProvider = services.BuildServiceProvider();
TaskScheduler _siteService = serviceProvider.GetService<TaskScheduler>();
_siteService.Run();
}
And this is the scheduler that uses it
public class TaskScheduler : ControllerBase
{
private readonly UserRepository userRepository;
private readonly IServiceScopeFactory serviceScopeFactory;
public TaskScheduler(UserRepository userRepository, IServiceScopeFactory serviceScopeFactory)
{
this.userRepository = userRepository;
this.serviceScopeFactory = serviceScopeFactory;
}
public void Run() //run at startup for debugging purpose
{
Thread threadRunAt2_O_Clock = new Thread(delegate ()
{
this.UpdateAllUserDisplayNames().Wait();
});
threadRunAt2_O_Clock.IsBackground = true;
threadRunAt2_O_Clock.Start();
}
protected async Task<bool> UpdateAllUserDisplayNames()
{
List<TUser> allUsers = (await this.userRepository.GetAll()).ToList();
foreach (TUser user in allUsers)
{
string newDisplayName = this.GetDisplayName(user.PkUserNt);
if (newDisplayName != "" && newDisplayName != user.Name)
{
user.Name = newDisplayName;
await this.userRepository.Update(user);
}
}
return true;
}
}
Exception:
A second operation was started on this context before a previous
operation completed. This is usually caused by different threads
concurrently using the same instance of DbContext. For more
information on how to avoid threading issues with DbContext, see
https://go.microsoft.com/fwlink/?linkid=2097913.
I am using Asp .Net 5 to create a WebApi and I am trying to put all database operations in a separate class, the problem is I can't Use ApplicationDbContext by initiating a new object because it takes an argument in the constructor.
my context :
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
{
}
controller :
[Route("api/[controller]")]
[ApiController]
public class AttributesController : ControllerBase
{
[HttpPost]
[Route("GetAllAttributes")]
public async Task<AllAttributes> GetAllAttributes()
{
return await new Services.AttributeService().GetAll();
}
}
service :
public class AttributeService
{
private readonly ApplicationDbContext _db ;
public async Task<AllAttributes> GetAll()
{
try
{
var dbAttributes = await _db.Attributes.Where(attr=> (bool)attr.IsActive && !(bool)attr.IsDeleted && !(bool)attr.IsTrashed).ToListAsync();
if (dbAttributes.Count>0)
{
return new AllAttributes
{
Attributes = dbAttributes,
Message = new ResponseMessage
{
Message = "Success",
Code = 200
}
};
}
else
{
return new AllAttributes
{
Message = new ResponseMessage
{
Message = "Empty",
Code = 410
}
};
}
}
catch (Exception ex)
{
return new AllAttributes
{
Message = new ResponseMessage
{
Message = ex.Message,
Code = 500
}
};
}
}}
so when I call it like this I got NullReference Exception.
You will need to add AttributeService to the DI container. You can do this inside ConfigureServices method of Startup.cs:
services.AddScoped<AttributeService>();
Then you can inject the context in the constructor of AttributeService:
public class AttributeService
{
private readonly ApplicationDbContext _db ;
public AttributeService(ApplicationDbContext db)
{
_db = db;
}
...
}
I am trying to implement an asp.net web API and it is my first time trying to implement such a project. I googled a lot.
Many times lost down the road because there is a lot of misguiding articles. Now I accomplished to implement the layered architecture. I have a business entity, business service, data models class library projects and a web API itself. As I said I googled a lot since I have no experience with web API, I decided to use generic repository and unit of work in my solution. My API is working butI get the following error when I load test with 400 users:
System.Transactions.TransactionException: The operation is not valid for the state of the transaction. ---> System.TimeoutException: Transaction Timeout. The underlying provider failed on Open.
I researched and asked experienced friends, they said I am not disposing of all the DBContexts I am creating. As I am using transient scope, a new DBcontext is created for every action call (controller construction), but unless I call context.Dispose() in the action the pools are not released.
Honestly, I am not sure how can I dispose of more.
Controller:
namespace Game.Controllers
{
[System.Web.Http.RoutePrefix("api/v2/game")]
public class GameController : ApiController
{
private readonly IGameServices _gameServices;
#region Public Constructor
public GameController(IGameServices gameServices)
{
_gameServices = gameServices;
}
#endregion
[HttpPost, Route("purchase")]
public async Task<IHttpActionResult> PurchaseGame(RequestDto game)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
//Call Services
var gameConfirmResponse = await _gameServices.GamePurchase(game);
switch (gameConfirmResponse.StatusCode)
{
case HttpStatusCode.NotFound:
{
return NotFound();
}
case HttpStatusCode.InternalServerError:
{
return InternalServerError();
}
case HttpStatusCode.OK:
{
if (gameConfirmResponse.Content == null)
{
return new System.Web.Http.Results.ResponseMessageResult(
Request.CreateErrorResponse((HttpStatusCode) 222, new HttpError("No Results Found")));
}
var responseStream = await gameConfirmResponse.Content.ReadAsStringAsync();
var resultResponse = JsonConvert.DeserializeObject<GameConfirmResponse>(responseStream);
if (resultResponse.coupons == null)
{
return new System.Web.Http.Results.ResponseMessageResult(
Request.CreateErrorResponse((HttpStatusCode) 222,
new HttpError("No Coupons Available for this Game")));
}
//Transform GameConfirmResponse into DTO for returning result
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<GameConfirmResponse, GameConfirmResponseDto>();
cfg.CreateMap<Coupon, CouponDto>();
});
var iMapper = config.CreateMapper();
var resultDto = iMapper.Map<GameConfirmResponse, GameConfirmResponseDto>(resultResponse);
return Ok(resultDto);
}
case HttpStatusCode.Unauthorized:
{
return Unauthorized();
}
case HttpStatusCode.RequestTimeout:
{
return InternalServerError();
}
}
return Ok(gameConfirmResponse);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
}
base.Dispose(disposing);
}
}
}
Service:
namespace BusinessService
{
public class GameServices : IGameServices, IDisposable
{
private readonly UnitOfWork _unitOfWork;
public GameServices(UnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public async Task<HttpResponseMessage> GamePurchase(RequestDto requestDto)
{
HttpResponseMessage response = null;
response = await CallRazerService(requestDto);
return response;
}
private async Task<HttpResponseMessage> CallRazerService(RequestDto requestDto)
{
HttpResponseMessage response = null;
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
//Added to see if I fix the error
TransactionInterop.GetTransmitterPropagationToken(Transaction.Current);
//Transform DTO into GameRequest for calling Razer Initiate
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<RequestDto, GameRequest>();
cfg.CreateMap<GameRequest, GameConfirmRequest>();
cfg.CreateMap<GameConfirmResponse, GameConfirmResponseDto>();
cfg.CreateMap<Coupon, CouponDto>();
cfg.CreateMap<GameRequest, GameRequestDto>();
});
var iMapper = config.CreateMapper();
var gameRequest = iMapper.Map<RequestDto, GameRequest>(requestDto);
//Unique reference ID
gameRequest.referenceId = Guid.NewGuid();
var gameRequestDto = iMapper.Map<GameRequest, GameRequestDto>(gameRequest);
//Create signature
gameRequest = Utilities.CreateSignature(gameRequestDto, RequestType.Initiate);
//Set service
gameRequest.service = "RAZER";
//Add initiation request into database
_unitOfWork.GameRepository.Insert(gameRequest);
#region Call Razer initiate/confirm
//Call Razer for initiation
response = await Utilities.CallRazer(gameRequest, "purchaseinitiation");
//Read response
var htmlResponse = await response.Content.ReadAsStringAsync();
var gameResponse = JsonConvert.DeserializeObject<GameResponse>(htmlResponse);
//Adding initiation response into database
_unitOfWork.GameResponseRepository.Insert(gameResponse);
if (gameResponse.initiationResultCode == "00")
{
gameRequestDto = iMapper.Map<GameRequest, GameRequestDto>(gameRequest);
gameRequestDto.validatedToken = gameResponse.validatedToken;
//Create signature
var gameConfirmRequest = Utilities.CreateSignature(gameRequestDto, RequestType.Confirm);
//Transform DTO into GameRequest for calling Razer Initiate
var gameConfirmRequests = iMapper.Map<GameRequest, GameConfirmRequest>(gameConfirmRequest);
//Add confirm request into database
_unitOfWork.GameConfirmRequestRepository.Insert(gameConfirmRequests);
//Call Razer for confirm
response = await Utilities.CallRazer(gameConfirmRequest, "purchaseconfirmation");
//Read response
htmlResponse = await response.Content.ReadAsStringAsync();
var gameConfirmResponse = JsonConvert.DeserializeObject<GameConfirmResponse>(htmlResponse);
//Set service
gameConfirmResponse.service = "RAZER";
//Add confirm response into database
_unitOfWork.GameConfirmResponseRepository.Insert(gameConfirmResponse);
}
#endregion
await _unitOfWork.SaveAsync();
scope.Complete();
}
return response;
}
public void Dispose()
{
_unitOfWork.Dispose();
}
}
}
Generic Repo (shortened):
namespace DataModels
{
public class GenericRepository<TEntity> where TEntity : class
{
internal GameContext context;
internal DbSet<TEntity> dbSet;
public GenericRepository(GameContext context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
#region Public member methods...
public virtual async Task<List<TEntity>> GetGamesAsync(
Expression<Func<TEntity, bool>> where)
{
return await dbSet.Where(where).ToListAsync();
}
public virtual async Task<List<T>> GetGameBankProducts<T, TType>(Expression<Func<TEntity, bool>> where,
Expression<Func<TEntity, TType>> groupBy, Expression<Func<IGrouping<TType, TEntity>, T>> select)
{
return await dbSet.Where(where)
.GroupBy(groupBy)
.Select(select)
.ToListAsync();
}
#endregion
}
}
Unit of Work:
namespace DataModels
{
public class UnitOfWork : IDisposable
{
private readonly GameContext _context;
private readonly GenericRepository<GameRequest> gameRequestRepository;
private readonly GenericRepository<GameResponse> gameResponseRepository;
private readonly GenericRepository<GameConfirmRequest> gameConfirmRequestRepository;
private readonly GenericRepository<GameConfirmResponse> gameConfirmResponseRepository;
private readonly GenericRepository<GameBank> gameBankRepository;
private readonly GenericRepository<GameBankPin> gameBankPinRepository;
private readonly GenericRepository<ConfirmCancel> confirmCancelRepository;
private readonly GenericRepository<Recon> reconRepository;
private readonly GenericRepository<Company> userRepository;
public UnitOfWork(GameContext context)
{
_context = context;
}
public GenericRepository<GameRequest> GameRepository
{
get
{
return this.gameRequestRepository ?? new GenericRepository<GameRequest>(_context);
}
}
public GenericRepository<GameResponse> GameResponseRepository
{
get
{
return this.gameResponseRepository ?? new GenericRepository<GameResponse>(_context);
}
}
public GenericRepository<GameConfirmRequest> GameConfirmRequestRepository
{
get
{
return this.gameConfirmRequestRepository ?? new GenericRepository<GameConfirmRequest>(_context);
}
}
public GenericRepository<GameConfirmResponse> GameConfirmResponseRepository
{
get
{
return this.gameConfirmResponseRepository ?? new GenericRepository<GameConfirmResponse>(_context);
}
}
public GenericRepository<GameBank> GameBankRepository
{
get
{
return this.gameBankRepository ?? new GenericRepository<GameBank>(_context);
}
}
public GenericRepository<GameBankPin> GameBankPinRepository
{
get
{
return this.gameBankPinRepository ?? new GenericRepository<GameBankPin>(_context);
}
}
public GenericRepository<ConfirmCancel> ConfirmCancelRepository
{
get
{
return this.confirmCancelRepository ?? new GenericRepository<ConfirmCancel>(_context);
}
}
public GenericRepository<Recon> ReconRepository
{
get
{
return this.reconRepository ?? new GenericRepository<Recon>(_context);
}
}
public GenericRepository<Company> UserRepository
{
get
{
return this.userRepository ?? new GenericRepository<Company>(_context);
}
}
public void Save()
{
_context.SaveChanges();
}
public async Task SaveAsync()
{
await _context.SaveChangesAsync();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
_context.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
Unity config:
namespace Game
{
public static class UnityConfig
{
#region Unity Container
private static Lazy<IUnityContainer> container =
new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterTypes(container);
return container;
});
public static IUnityContainer Container => container.Value;
#endregion
public static void RegisterTypes(IUnityContainer container)
{
// Default empty Time manager!
container.RegisterType<IGameServices, GameServices>().RegisterType<UnitOfWork>(new TransientLifetimeManager());
container.RegisterType<IUserValidate, UserValidate>(new TransientLifetimeManager());
}
}
}
People keep telling me the best practice is to use "using" but how can I use using in the controller for the Dbcontext? I couldn't find a way.
Update1:
I have been told that instead of transaction scope, I should use dbcontext transaciton (using(var dbContextTransaction = context.Database.BeginTransaction())). I couldn't find a way to use dbcontext transaction in my Service class. I am injecting unit of work in Service class.
Update2:
Should I implement inside of unit of work and remove transactionscope inside of the Service?
UOW Save:
public void Save(){
try{
using (var transaction = _context.Database.BeginTransaction()){
try
{
_context.SaveChanges();
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
}catch (Exception e)
{//TODO:Logging}}
I am having issues where my DbContext is being disposed of early. It is only ever apparent when calling any of the *Async methods, such as ToListAsync() - if i call any of the syncronous methods everything is fine.
I can't figure out what I'm doing wrong.
Any advise please?
Here is as much of the code as i believe is needed.
The DbContext and its interface
public interface IMyDbContext
{
DbSet<MyModel> MyModels { get; set; }
}
public class MyDbContext : DbContext, IMyDbContext
{
public DbSet<MyModel> MyModels { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
}
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new MyModelConfig());
}
}
A Repository using this DbContext
public class MyModelRepository : IMyModelRepository
{
private readonly IMyDbContext _dbContext;
private string _baseSql = "Some SQL here ";
public MyModelRepository(IMyDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<IList<MyModel>> GetAllAsync(Paging paging, Permission permission)
{
if (permission == null)
throw new ArgumentNullException("permission");
string sql = ApplyFilter(_baseSql, permission);
try
{
// THIS FAILS
return await _dbContext.MyModels.FromSql(sql).Skip(paging.Skip).Take(paging.Take).ToListAsync();
// THIS FAILS
return await _dbContext.MyModels.FromSql(sql).ToListAsync();
// THIS WORKS
return await _dbContext.MyModels.FromSql(sql).ToList();
}
catch (Exception e)
{
throw new InvalidOperationException("Could not retrieve data", e);
}
}
}
I'm calling the repo via a service that looks like this:
public class GetAllMyModelQuery : IGetAllMyModelQuery
{
private readonly IMyModelRepository _myModelRepository;
private readonly IPermissionService _permissionService;
private readonly ILogger _logger;
public GetAllAbsenceQuery(IMyModelRepository myModelRepository, IPermissionService permissionService, ILogger<GetAllMyModelQuery> logger)
{
_myModelRepository = myModelRepository;
_permissionService = permissionService;
_logger = logger;
}
public async Task<IList<Emp_AbsenceEtrac>> Execute(Paging paging)
{
if (_permissionService.Permission == null)
{
_logger.LogInformation("No permission to the requested resource");
return null;
}
// if external?
// call external repo
//TODO//
// else
return await _myModelRepository.GetAllAsync(paging, _permissionService.Permission);
}
}
This in turn is called by the controller
public class MyModelController : Controller
{
private readonly IQueryStore _queryStore;
public MyModelController(IQueryStore queryStore)
{
_queryStore = queryStore;
}
[HttpGet]
[ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(BadRequestObjectResult), (int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> Index([FromQuery] int offset = 0, [FromQuery] int limit = 25)
{
Paging paging = new Paging(offset, limit);
return Ok(_queryStore.GetAllMyModelQuery.Execute(paging));
}
}
Finally, it's all wired together in the startup:
services.AddScoped<IMyDbContext, MyDbContext>();
services.AddScoped<IMyModelRepository, MyModelRepository>();
// Everything else above is also added as scope..
services.AddDbContext<MyDbContext>(opts =>
{
opts.UseSqlServer(Configuration.GetConnectionString("MyDb"),
sqlServerOptions =>
{
sqlServerOptions.CommandTimeout(600);
// required to allow skip/take on sql server 2008
sqlServerOptions.UseRowNumberForPaging(true);
});
});
Is there anything jumping out that would cause my Async calls to result in a closed Db connection?
Error is:
You should await the GetAllMyModelQuery.Execute method in your Index controller action:
[HttpGet]
[ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(BadRequestObjectResult), (int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> Index([FromQuery] int offset = 0, [FromQuery] int limit = 25)
{
Paging paging = new Paging(offset, limit);
return Ok(await _queryStore.GetAllMyModelQuery.Execute(paging).ConfigureAwait(false));
}