I'm working on an ASP.NET Core MVC application and I have a view who do a post request as:
$.ajax({
url:'/Advertisers/ActiveAdvertiser?id='+id+'&isActive='+!isActive,
method: 'POST',
success: function(r){
Swal.fire("Inactivated!", "Advertiser inactivated successfully", "success");
},
error: function (request) {
console.log(request.responseText)
Swal.fire("Error!", "Something went wrong, please try again`", "warning");
}
});
Controller:
[HttpPost]
public async Task<JsonResult> ActiveAdvertiser(int id, bool isActive)
{
var advertiser = await _advertisersService.GetAdvertiserByAdvertiserIdAsync(id);
if (advertiser != null)
{
var model = AssingAdvertiserViewModel(advertiser, id);
model.IsActive = isActive;
var result = await _advertisersService.UpdateAdvertiserAsync(model, GetCurrentUserAsync().Id);
if (result != null)
{
return Json(new { result = "OK" });
}
}
return Json(new { result = "BadRequest" });
}
Post method services:
public Task<Advertiser?> GetAdvertiserByAdvertiserIdAsync(int advertiserId)
{
return _db.Advertisers
.Include(a => a.Address)
.Include(pc => pc.PrimaryContact)
.Include(ac => ac.AlternateContact)
.FirstOrDefaultAsync(x => x.AdvertiserId == advertiserId);
}
private AdvertiserViewModel AssingAdvertiserViewModel(Advertiser advertiser, int id)
{
var model = new AdvertiserViewModel()
{
//Fill model here
};
return model;
}
public async Task<Advertiser?> UpdateAdvertiserAsync(AdvertiserViewModel model, int updatedById)
{
var advertiser = await GetAdvertiserByAdvertiserIdAsync(model.AdvertiserId);
if (advertiser is null)
return null;
advertiser.Name = model.Name;
// fill model here
await _db.SaveChangesAsync();
return advertiser;
}
The problem is I do the first request, and it returns Success with any issues, but if I try to do a second one, it throws an exception:
System.InvalidOperationException: A second operation was started on
this context instance before a previous operation completed. This is
usually caused by different threads concurrently using the same
instance of DbContext.
If I stop the project and run it again it works one time again and in the second time get the error again
I read about this issue in other questions, and apparently is because you don't use the await services, I check my code and almost everything uses await. Can someone see something that I don't see? Regards
You could check this document for how to handle this error:
Therefore, always await async calls immediately, or use separate DbContext instances for operations that execute in parallel.
So you could check if misssing the await keyword on async operation
and use separate Dbcontext instances with DbcontextFactory as below:
regist the factory in service collection:
builder.Services.AddDbContextFactory<SomeContext>();
inject it into controller/Service/Somewhereelse:
public class SomeEntitiesController : Controller
{
private readonly IDbContextFactory<SomeContext> _factory;
public SomeEntitiesController(IDbContextFactory<SomeContext> factory)
{
_factory = factory;
}
}
create a new dbcontext:
_factory.CreateDbContext()
I solve this by adding ServiceLifetime.Transient into my services as:
services.AddDbContext<ApplicationDbContext>(
options =>
options.UseSqlServer(Configuration.GetConnectionString("ApplicationDbConnection")),
ServiceLifetime.Transient
);
Related
I have ASP.NET Web API controller with some actions (methods). Let's say something like this:
[HttpPost]
public async Task<ActionResult> SavePerson([FromBody]PersonDto person)
{
await _mediatr.Send(new SavePerson.Command(person));
return Ok();
}
and the PersonDto looks something like this:
public record PersonDto([Required, MinLength(3)]string Name, int? Age);
When I call my Web API action 'SavePerson' with invalid person data (Name.Length < 3 and etc...), ASP.NET Core model binding validation interrupts the execution and returns 400 (Bad Request) as it should. When I pass valid person data, it works fine.
My questions are:
How can I catch this model binding validation result (400 Bad Request) and transform it into different format, so our front-end developers will be happy?
Should I validate my DTOs (PersonDto) in Web API layer or it's better to validate it in MediatR command handler? I'm trying to adhere Uncle Bob's Clean Architecture. I have Domain, Application, Infrastructure, Web API. And my MediatR CQRS handlers are placed in the Application layer.
Automatic 400 bad request responses is enabled by default. To disable it use the following code in Startup ConfigureServices method:
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
Then you can handle invalid model states manually like this:
[HttpPost]
public async Task<ActionResult> SavePerson([FromBody]PersonDto person)
{
if(!ModelState.IsValid)
return BadRequest(ModelState);// or what ever you want
await _mediatr.Send(new SavePerson.Command(person));
return Ok();
}
You can use Jason Taylor's Clean Architecture approach. Instead of using attribute validation, use FluentValidation:
public class CreatePersonCommandValidator : AbstractValidator<SavePerson.Command>
{
public CreatePersonCommandValidator()
{
RuleFor(v => v.Title)
.NotEmpty().WithMessage("Title is required.")
.MinimujLength(200).WithMessage("Title at least should have 3 characters.");
}
}
Use MediatR behavior to perform validation and translate errors into validation exception:
public class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehaviour(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
if (_validators.Any())
{
var context = new ValidationContext<TRequest>(request);
var validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
var failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToList();
if (failures.Count != 0)
throw new ValidationException(failures);
}
return await next();
}
}
Validation exception:
public class ValidationException : Exception
{
public ValidationException()
: base("One or more validation failures have occurred.")
{
Errors = new Dictionary<string, string[]>();
}
public ValidationException(IEnumerable<ValidationFailure> failures)
: this()
{
Errors = failures
.GroupBy(e => e.PropertyName, e => e.ErrorMessage)
.ToDictionary(failureGroup => failureGroup.Key, failureGroup => failureGroup.ToArray());
}
public IDictionary<string, string[]> Errors { get; }
}
And finally, implement an exception filter or exception handling middleware to catch that exception and return desired response:
public class ApiExceptionFilterAttribute : ExceptionFilterAttribute
{
private readonly IDictionary<Type, Action<ExceptionContext>> _exceptionHandlers;
public ApiExceptionFilterAttribute()
{
// Register known exception types and handlers.
_exceptionHandlers = new Dictionary<Type, Action<ExceptionContext>>
{
{ typeof(ValidationException), HandleValidationException }
};
}
public override void OnException(ExceptionContext context)
{
HandleException(context);
base.OnException(context);
}
private void HandleException(ExceptionContext context)
{
Type type = context.Exception.GetType();
if (_exceptionHandlers.ContainsKey(type))
{
_exceptionHandlers[type].Invoke(context);
return;
}
if (!context.ModelState.IsValid)
{
HandleInvalidModelStateException(context);
return;
}
HandleUnknownException(context);
}
private void HandleValidationException(ExceptionContext context)
{
var exception = context.Exception as ValidationException;
//var details = new ValidationProblemDetails(exception.Errors)
//{
//Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1"
//};
context.Result = Returns your response type //new BadRequestObjectResult(details);
context.ExceptionHandled = true;
}
}
You can perform ModelState.isValid() in the biginig of your Api method and return a BadRequestResult() if the model is invalid. You can can return the validation errors along with BadRequestResult.
You need to get validation errors from model state and fill your custom error object. This way your customers can see more meaningful errors.
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.
I'm trying to use Redis for my API. And here is the problem.
I'm trying this:
[HttpGet]
public async Task<ActionResult<IEnumerable<Writer>>> GetAllWriters([FromServices] IWriterRepository repository, [FromServices] IDistributedCache cache)
{
string recordKey = "Writers_" + DateTime.Now.ToString("yyyyMMdd_hhmm");
var writers = await cache.GetRecordAsync<IEnumerable<Writer>>(recordKey);
if (writers == null)
{
writers = repository.GetAll();
await cache.SetRecordAsync(recordKey, writers, TimeSpan.FromSeconds(180));
}
return Ok(writers);
}
but I'm getting the error
There is already an open DataReader associated with this Connection which must be closed first.
I'm a little confused by this error because the EF call can fetch my data, I can save this data to redis and I "can" get the data back from it as well, just need to figure it out the SOLID problem with private set hehe.
I'm open for suggestions :D.
Here is the redis implementation
using System;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
namespace Worthix.Api.Extensions
{
public static class DistributedCacheExtensions
{
public static async Task SetRecordAsync<T>(this IDistributedCache cache,
string recordId, T data, TimeSpan? absoluteExpireTime = null, TimeSpan? unusedExpireTime = null)
{
var options = new DistributedCacheEntryOptions();
options.AbsoluteExpirationRelativeToNow = absoluteExpireTime ?? TimeSpan.FromSeconds(60);
options.SlidingExpiration = unusedExpireTime;
var jsonData = JsonSerializer.Serialize(data);
await cache.SetStringAsync(recordId, jsonData, options);
}
public static async Task<T> GetRecordAsync<T>(this IDistributedCache cache, string recordId)
{
var jsonData = await cache.GetStringAsync(recordId);
if (jsonData == null)
return default(T);
return JsonSerializer.Deserialize<T>(jsonData);
}
}
}
and his startup
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers().AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
// services.AddDbContext<DataContext>(opt => opt.UseInMemoryDatabase("database"));
services.AddDbContext<DataContext>(opt => opt.UseSqlServer(Configuration.GetConnectionString("connectionString")));
services.AddScoped<IWriterRepository, WriterRepository>();
services.AddTransient<IBookRepository, BookRepository>();
services.AddTransient<WriterHandler, WriterHandler>();
services.AddTransient<BookHandler, BookHandler>();
services.AddStackExchangeRedisCache(opt =>
{
opt.Configuration = Configuration.GetConnectionString("connectionStringRedis");
opt.InstanceName = "Worthix_";
});
}
I hope there is enough info.
Just an update.
I figure it out. I still don't know why this error was happening.
I refactored EF Core queries to async and it worked, if anyone have any ideia why this errors happened, I will mark as the correct answer.
[HttpGet]
public async Task<ActionResult<IEnumerable<Writer>>> GetAllWriters([FromServices] IWriterRepository repository, [FromServices] IDistributedCache cache)
{
string recordKey = "Writers_All_" + DateTime.Now.ToString("yyyyMMdd_hhmm");
var writersModel = await cache.GetRecordAsync<IEnumerable<WriterModel>>(recordKey);
if (writersModel == null)
{
var writers = await repository.GetAll();
await cache.SetRecordAsync(recordKey, writers);
return Ok(writers);
}
return Ok(writersModel);
}
A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.
My unitofwork code
public class UnitOfWork : IUnitOfWork
{
private readonly CAMSDbEntities _context;
private bool _disposed;
public Dictionary<Type, object> repositories = new Dictionary<Type, object>();
private Guid _objectId;
public UnitOfWork(IContextFactory contextFactory)
{
_context = contextFactory.DbContext as CAMSDbEntities;
_objectId = Guid.NewGuid();
}
public IGenericRepository<T> Repository<T>() where T : class
{
if (repositories.Keys.Contains(typeof(T)) == true)
{
return repositories[typeof(T)] as GenericRepository<T>;
}
GenericRepository<T> repo = new GenericRepository<T>(_context);
repositories.Add(typeof(T), repo);
return repo;
}
My unity config
container.RegisterType<IHttpContext, HttpContextObject>();
container.RegisterType<IDataBaseManager, DataBaseManager>();
container.RegisterType<IContextFactory, ContextFactory>();
container.RegisterType(typeof(IGenericRepository<>), typeof(GenericRepository<>));
container.RegisterType<IUnitOfWork, UnitOfWork>();
container.RegisterType<IAnalytics, DashbordService>();
GlobalConfiguration.Configuration.DependencyResolver = new UnityDependencyResolver(container);
webApi Controller
public class DashbordController : ApiController
{
private static IAnalytics _analytics;
public DashbordController(IAnalytics dashbordService)
{
_analytics = dashbordService;
}
[HttpGet]
[Route("GetStudentAssessmentHistory")]
public IHttpActionResult GetStudentAssessmentHistory(int studentID)
{
var result = _analytics.GetStudentAssessmentHistoryGraphData(studentID);
return Ok(result);
}
[HttpGet]
[Route("GetStudentFeePaymentHistory")]
public async Task<IHttpActionResult> GetStudentFeePaymentData(int studentID)
{
var result = await _analytics.GetStudentFeePaymentData(studentID);
return Ok(result);
}
[HttpGet]
[Route("GetLedgerHitoryByDepartment")]
public async Task<IHttpActionResult> GetLedgerHitoryByDepartment(int schoolID, int departmentId)
{
var result = await _analytics.GetLedgerHitory(schoolID, departmentId);
return Ok(result);
}
[HttpGet]
[Route("GetLedgerExpenseTrendByDepartment")]
public async Task<IHttpActionResult> GetLedgerExpenseTrendByDepartment(int schoolID)
{
var result = await _analytics.GetLedgerExpenseTrend(schoolID);
return Ok(result);
}
dashboardservice Code
public async Task<List<LedgerExpense>> GetLedgerExpenseTrend(int schoolId)
{
try
{
var ledgerExpenses = new List<LedgerExpense>();
var currentDate = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, INDIAN_ZONE);
DateTime previoYearDate = currentDate.AddYears(-1);
var ledgerPayments = await _unitOfWork.Repository<LedgerDetail>().GetManyAsync(x => x.SchoolID == schoolId && x.PaymentDate <= currentDate
&& x.PaymentDate >= previoYearDate);
foreach (var ledgerPayment in ledgerPayments.OrderBy(x => x.PaymentDate).GroupBy(y => y.DepartmentID))
{
var department = await _unitOfWork.Repository<DeptartmentType>().GetAsync(x => x.ID == ledgerPayment.Key);
var ledgerData = new LedgerExpense
{
Department = department.DepartmentName,
TotalLedgerExpense = 0
};
foreach (var departmentPayment in ledgerPayment)
{
ledgerData.TotalLedgerExpense += departmentPayment.TotalPaidAmount;
}
ledgerExpenses.Add(ledgerData);
}
return ledgerExpenses;
}
catch (Exception ex)
{
logger.Log("An error occurred while fetching ledger expenses");
return null;
}
}
I have similar type of asynchronous metods implemented in my dashboardservice code. whenever I request a dashboard UI all request comes to the same controller at the same time and creates the new object for unitofwork and dbcontext for each request one by one. it works perfectly sometimes but Sometimes I think unitofwork and dbcontext object flows with the wrong thread and throws this error. I think somehow its picking wrong dbcontext which is already busy with someother api request from dashboard service.
Please remove the static keyword in your controller from this code:
private static IAnalytics _analytics;`
Once that has been created, it will never be created again unless the application pool is recycled (manual or IIS restart etc.) Since you are using the same instance for all requests, you are getting that error at random. If a request finishes before the next one arrives, it will NOT result in an error. Otherwise it will. Hence the reason for not always getting the error (as you mention in your question).
Please read about how static affects the design in a web scenario (or server).
Try and think of web requests as a single transaction, all classes are created for each request and then thrown away after the request has been served. That means if you have static or any other mechanism which is for sharing, it will be shared between requests.
I'm trying to figure out the best way to wait for some number of I/O Completion ports to complete.
For this scenario, let's say that I'm in a MVC3 web app. (My understanding is the use of I/O Completion ports here is recommended so I can return the original thread back to IIS to service other requests)
Lets say I have an array of IDs and I want to fetch an object for each ID from some network call.
What is the best way to parallelize this synchronous method?
public class MyController: Controller
{
public ActionResult Index(IEnumerable<int> ids)
{
ids.Select(id => _context.CreateQuery<Order>("Orders")
.First(o => o.id == id));
DataServiceQuery<Order> query = _context.CreateQuery<Order>("Orders");
return Json(query);
}
private DataServiceContext _context; //let's ignore how this would be populated
}
I know it would start like this:
public class MyController: AsyncController
{
public void IndexAsync(IEnumerable<int> ids)
{
// magic here...
AsyncManager.Sync(() => AsyncManager.Parameters["orders"] = orders);
}
public ActionResult IndexCompleted(IEnumerable<Order> orders)
{
return Json(orders);
}
private DataServiceContext _context; //let's ignore how this would be populated
}
Should I be using DataServiceContext.BeginExecute Method? DataServiceContext.BeginExecuteBatch ? The data service I'm consuming can only get one record at a time (this is beyond my control) and I want these individual queries to run in parallel.
This is the pattern I've ended up using for running a batch of async operations inside MVC3:
public class MyController: AsyncController
{
public void IndexAsync(int[] ids)
{
var orders = new Orders[ids.Length];
AsyncManager.Parameters["orders"] = orders;
// tell the async manager there are X operations it needs to wait for
AsyncManager.OutstandingOperations.Increment(ids.Length);
for (int i = 0; i < ids.Length; i++){
var index = i; //<-- make sure we capture the value of i for the closure
// create the query
var query = _context.CreateQuery<Order>("Orders");
// run the operation async, supplying a completion routine
query.BeginExecute(ar => {
try {
orders[index] = query.EndExecute(ar).First(o => o.id == ids[index]);
}
catch (Exception ex){
// make sure we send the exception to the controller (in case we want to handle it)
AsyncManager.Sync(() => AsyncManager.Parameters["exception"] = ex);
}
// one more query has completed
AsyncManager.OutstandingOperations.Decrement();
}, null);
}
}
public ActionResult IndexCompleted(Order[] orders, Exception exception)
{
if (exception != null){
throw exception; // or whatever else you might like to do (log, etc)
}
return Json(orders);
}
private DataServiceContext _context; //let's ignore how this would be populated
}
Using TPL is easy.
public ActionResult Index(IEnumerable<int> ids)
{
var result = ids.AsParallel()
.Select(id => GetOrder(id))
.ToList();
return Json(result);
}
Order GetOrder(int id) { ... }