I have .Net Core 3.1 application that is using EF Core 3.1.9. During a specific process I am getting the following error:
A second operation started on this context before a previous operation completed. This is usually caused by different threads 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 Dependency Injection for the DbContext and have gone through all the flows to make sure everything is properly and immediately await'ed.
The error occurs within LtiUserRepository.cs which will be shown below.
That process starts with an external http call using an HttpClient that has a custom MessageHandler, registered in Startup.cs:
services.AddHttpClient<MyRepository>("MyCustomUserClient", client =>
{
var canvasUrl = Configuration.GetSection("Urls:Removed").Value ?? "https://example.com/";
client.BaseAddress = new System.Uri(removed);
}).AddHttpMessageHandler<LtiUserApiAuthenticationHttpClientHandler>();
The code that initiates the HTTP Call is:
public async Task<PlatformQuizSubmissions> GetUserQuiz(string courseId, string quizId)
{
var path = $"api/v1/courses/{courseId}/quizzes/{quizId}/submission";
var response = await _myCustomUserClient.GetAsync(path);
// some stuff
var responseContent = await response.Content.ReadAsStringAsync();
// Some other stuff
}
The purpose of the custom MessageHandler is to check for a header, get some data, and append a query parameter to each request
public sealed class LtiUserApiAuthenticationHttpClientHandler : DelegatingHandler
{
private readonly IHttpContextAccessor _accessor;
private readonly ILtiUserService _userService;
public LtiUserApiAuthenticationHttpClientHandler(IHttpContextAccessor accessor, ILtiUserService ltiUserService)
{
_accessor = accessor;
_userService = ltiUserService;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var obo = _accessor.HttpContext.Request.Headers["QT-OBO"];
// THIS IS THE PART THAT QUERIES THE DATABASE
var user = await _userService.Get(new Guid(obo));
var uriBuilder = new UriBuilder(request.RequestUri);
if (string.IsNullOrEmpty(uriBuilder.Query))
{
uriBuilder.Query = $"as_user_id={user.PlatformUserId}";
}
else
{
uriBuilder.Query = $"{uriBuilder.Query}&as_user_id={user.PlatformUserId}";
}
request.RequestUri = uriBuilder.Uri;
return await base.SendAsync(request, cancellationToken);
}
}
You can see above that the MessageHandler calls _userservice.Get, which is this:
public async Task<LtiUser> Get(Guid guid)
{
return await _ltiUserRepository.Get(guid);
}
That simply returns from the repository, which is this:
public class LtiUserRepository : ILtiUserRepository
{
private readonly SqlDbContext _db;
private readonly IMapper _mapper;
private readonly ILogger<LtiUserRepository> _logger;
public LtiUserRepository(SqlDbContext sqlDbContext, IMapper mapper, ILoggerFactory logger)
{
_db = sqlDbContext;
_mapper = mapper;
_logger = logger != null ? logger.CreateLogger<LtiUserRepository>() : throw new ArgumentNullException(nameof(logger));
}
public async Task<LtiUser> Get(Guid guid)
{
try
{
return await _db.LtiUsers
.AsNoTracking()
.Where(l => l.UUID == guid)
.ProjectTo<LtiUser>(_mapper.ConfigurationProvider)
.SingleOrDefaultAsync();
}
catch (Exception ex)
{
// This is where the error is caught.
_logger.LogCritical($"Could not get LtiUser via (UUID) {guid} : {ex.Message}");
return null;
}
}
}
The database is registered in Startup.cs with:
protected virtual void ConfigureDatabaseServices(IServiceCollection services)
{
services.AddDbContext<SqlDbContext>(
o => o.UseSqlServer(Configuration.GetConnectionString("DbConnectionString")),
ServiceLifetime.Transient);
}
When I hit this endpoint using ApacheBench with 20 requests, concurrency of 2 I get this error anywhere from 2 to 10 times. However, looking at the following snippet from the MessageHandler (LtiUserApiAuthenticationHttpClientHandler) again:
var user = await _userService.Get(new Guid(obo));
if (string.IsNullOrEmpty(uriBuilder.Query))
{
uriBuilder.Query = $"as_user_id={user.PlatformUserId}";
}
else
{
uriBuilder.Query = $"{uriBuilder.Query}&as_user_id={user.PlatformUserId}";
}
If I replace user.PlatformUserId with a hardcoded, known value, (and comment out the call to _userService.Get) I can use AB with 1000 requests and a concurrency of 20 and have 0 occurrences of the issue. That leads me to believe I have it narrowed down to the offending flow, but am not sure of the correct way to do this.
Related
Here I am trying to invoke a dialog and push it on the existing dialog stack, idea is to proactively call a Dialog and continue the waterfall structure of the dialog.
Issue : I can start the dialog, and prompt user with Hero card to select Yes or No choices
but the Answer given by user does not stay in the same Dialog context.
Error : Failed to continue dialog. A dialog with continueConversationDialog id could not be found.
Below is the sample code snippet used for invoking the Dialog from controller
public class ProactiveMessageController : Controller
{
private readonly BotFrameworkHttpAdapter _adapter;
private readonly ConversationState _conversationState;
private readonly IBot _bot;
private readonly BotSettings _settings;
private readonly ILastMessageSent _lastmessagesent;
private readonly ContinueConversationDialog _dialog;
public ProactiveMessageController (BotFrameworkHttpAdapter adapter, ConversationState conversationState, IBot bot, BotSettings settings, ILastMessageSent lastmessagesent, ContinueConversationDialog dialog)
{
_adapter = adapter;
_conversationState = conversationState;
_bot = bot;
_settings = settings;
_dialog = dialog;
}
[HttpGet]
public async Task<IActionResult> Index()
{
try
{
await _adapter.ContinueConversationAsync(
_settings.MicrosoftAppId,
item.conversationReference,
async (ITurnContext TurnContext, CancellationToken cancellationToken) =>
{
var conversationStateAccessors = _conversationState.CreateProperty<DialogState>(nameof(DialogState));
var dialogSet = new DialogSet(conversationStateAccessors);
dialogSet.Add(_dialog);
var dialogContext = await dialogSet.CreateContextAsync(TurnContext, cancellationToken);
await dialogContext.BeginDialogAsync(_dialog.Id);
await _conversationState.SaveChangesAsync(TurnContext,false,cancellationToken);
},
default(CancellationToken)
);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
Sample waterfall Dialog code snippet, is as follows
public class ContinueConversationDialog : ComponentDialog
{
private readonly BotServices _services;
private readonly LocaleTemplateManager _templateManager;
private readonly IStatePropertyAccessor<UserProfileState> _accessor;
private readonly IUserManagement _userManagement;
public ContinueConversationDialog(
IServiceProvider serviceProvider, IUserManagement userManagement)
: base(nameof(ContinueConversationDialog))
{
_templateManager = serviceProvider.GetService<LocaleTemplateManager>();
var userState = serviceProvider.GetService<UserState>();
_accessor = userState.CreateProperty<UserProfileState>(nameof(UserProfileState));
_services = serviceProvider.GetService<BotServices>();
var ContinueConversation = new WaterfallStep[]
{
PromptToContinueConversation,
FinishDialogAsync,
};
AddDialog(new WaterfallDialog(nameof(ContinueConversation), ContinueConversation));
AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt)));
_userManagement = userManagement;
}
public async Task<DialogTurnResult> PromptToContinueConversation(WaterfallStepContext sc, CancellationToken cancellationToken)
{
return await sc.PromptAsync(nameof(ConfirmPrompt), new PromptOptions
{
Prompt = _templateManager.GenerateActivityForLocale("ConfirmPrompt"),
RetryPrompt = _templateManager.GenerateActivityForLocale("InvalidOptionPrompt"),
Style = ListStyle.HeroCard
}, cancellationToken);
}
public async Task<DialogTurnResult> FinishDialogAsync(WaterfallStepContext sc, CancellationToken cancellationToken)
{
bool ContinueConversationresponse = (bool)sc.Result;
if (ContinueConversationresponse)
{
//Continue the conversation on main dialog
Console.Write("Selected Yes");
return null;
}
else
{
//Ask for feedback and close the conversation
return null;
}
}
}
Part 1 where user is prompted with chocie works well, dialog is invoked without any issue
But when user submits the response I am not able to get it in same context.
I apply debugger and breakpoints in dialog to confirm the flow reaching the second step but it never does.
Can you please suggest how can we continue the conversation in same context after dialog is pushed on the current stack.
FYI: I am saving the current dialog state as well but no luck continuing; Dialog classes are registered as Transient, I have tried making it Singleton but does not make a differnce.
I have an asmx Web Service and I am using async Task. My problem is whenever I reached on the PostAsync statement it will just end there and fire a result to the browser with an empty result. Which is not I want. I tried passing the httpclient as a parameter to my service class thinking it may solved the issue.
I tried putting ConfigureAwait(false) and it gives a result however I don't want this because I need to return the value to the user. If I use ConfigurAwait(false) it will return an empty result to the browser even if it it still not completed. Am I doing this right? Thanks
in my webmethod
public class WebService1 : WebService
{
HttpClient Client = new HttpClient();
XDocument doc = new XDocument();
[WebMethod]
private async Task<String> Sample1(string a, int b)
{
myServiceClass _ms = new myServiceClass(Client);
var message = await _ms.GetResponseMessageAsync(a,b);
doc = await _ms.ReadResponseAsync(message); // It will not reach here if I don't use ConfigureAwait(false)
return JsonConvert.SerializeObject(doc);
}
}
myServiceClass.cs
public class myServiceClass
{
HttpClient _client;
public myServiceClass(HttpClient client)
{
_client = client;
}
public async Task<HttpResponseMessage> GetResponseMessageAsync(string a, int b)
{
HttpResponseMessage message;
httpcontent = (a,encoding.UTF8,"text/xml"); //This is just a sample content
message = await _client.PostAsync(UrlString, httpcontent); //<= here it stops and return empty result if there is no ConfigureAwait(false).
if (!message.IsSuccessStatusCode)
{
throw new HttpRequestException($"Cannot connect to api: {message.StatusCode} , {message.ReasonPhrase}");
}
return message; // It will not reach here if I don't use ConfigureAwait(false)
}
}
Is there a way to get the contents of the http request before deciding what kind of response I want to send back for the test? Multiple tests will use this class and each test will have multiple http requests.
This code does not compile because the lambda is not async and there is an await in it. I'm new to async-await, so I'm not sure how to resolve this. I briefly considered having multiple TestHttpClientFactories, but that would mean duplicated code, so decided against it, if possible.
Any help is appreciated.
public class TestHttpClientFactory : IHttpClientFactory
{
public HttpClient CreateClient(string name)
{
var messageHandlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);
messageHandlerMock.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync((HttpRequestMessage request, CancellationToken token) =>
{
HttpResponseMessage response = new HttpResponseMessage();
var requestMessageContent = await request.Content.ReadAsStringAsync();
// decide what to put in the response after looking at the contents of the request
return response;
})
.Verifiable();
var httpClient = new HttpClient(messageHandlerMock.Object);
return httpClient;
}
}
To take advantage of the async delegate use the Returns method instead
public class TestHttpClientFactory : IHttpClientFactory {
public HttpClient CreateClient(string name) {
var messageHandlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);
messageHandlerMock.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.Returns(async (HttpRequestMessage request, CancellationToken token) => {
string requestMessageContent = await request.Content.ReadAsStringAsync();
HttpResponseMessage response = new HttpResponseMessage();
//...decide what to put in the response after looking at the contents of the request
return response;
})
.Verifiable();
var httpClient = new HttpClient(messageHandlerMock.Object);
return httpClient;
}
}
Or consider creating your own handler that exposes a delegate to handle the desired behavior.
For example
public class DelegatingHandlerStub : DelegatingHandler {
private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _handlerFunc;
public DelegatingHandlerStub() {
_handlerFunc = (request, cancellationToken) => Task.FromResult(request.CreateResponse(HttpStatusCode.OK));
}
public DelegatingHandlerStub(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> handlerFunc) {
_handlerFunc = handlerFunc;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
return _handlerFunc(request, cancellationToken);
}
}
And used in the factory like this
public class TestHttpClientFactory : IHttpClientFactory {
public HttpClient CreateClient(string name) {
var messageHandlerMock = new DelegatingHandlerStub(async (HttpRequestMessage request, CancellationToken token) => {
string requestMessageContent = await request.Content.ReadAsStringAsync();
HttpResponseMessage response = new HttpResponseMessage();
//...decide what to put in the response after looking at the contents of the request
return response;
});
var httpClient = new HttpClient(messageHandlerMock);
return httpClient;
}
}
I have implemented a subclass of AuthenticationHandler. It returns AuthenticationResult.Fail("This is why you can't log in");
I would have expected this message to end up in the body, or at least in the HTTP status text, but instead I get a blank 401 response.
Is there any way to provide additional information for failed authentication attempts in ASP.NET core?
Override HandleChallengeAsync:
In the example below the failReason is a private field in my implementation of AuthenticationHandler.
I don't know if this is the best way to pass the reason for failure. But the AuthenticationProperties on the AuthenticateResult.Fail method did not make it through to HandleChallengeAsync in my test.
public class CustomAuthenticationHandler<TOptions> : AuthenticationHandler<TOptions> where TOptions : AuthenticationSchemeOptions, new()
{
private string failReason;
public CustomAuthenticationHandler(IOptionsMonitor<TOptions> options
, ILoggerFactory logger
, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) { }
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
failReason = "Reason for auth fail";
return AuthenticateResult.Fail(failReason);
}
protected override Task HandleChallengeAsync(AuthenticationProperties properties)
{
Response.StatusCode = 401;
if (failReason != null)
{
Response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = failReason;
}
return Task.CompletedTask;
}
}
From the docs: https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.authenticationhandler-1?view=aspnetcore-2.2
Override this method to deal with 401 challenge concerns, if an authentication scheme in question deals an authentication interaction as part of it's request flow. (like adding a response header, or changing the 401 result to 302 of a login page or external sign-in location.)
Source:
https://github.com/aspnet/Security/blob/master/src/Microsoft.AspNetCore.Authentication/AuthenticationHandler.cs#L201
I used this code in my custom Middleware to return problemDetails response.
public async Task Invoke(HttpContext httpContext)
{
await this.Next(httpContext);
if (httpContext.Response.StatusCode == StatusCodes.Status401Unauthorized)
{
var authenticateResult = await httpContext.AuthenticateAsync();
if (authenticateResult.Failure != null)
{
var routeData = httpContext.GetRouteData() ?? new RouteData();
var actionContext = new ActionContext(httpContext, routeData, new ActionDescriptor());
var problemDetails = this.ProblemDetailsFactory.CreateProblemDetails(httpContext,
statusCode: httpContext.Response.StatusCode,
detail: authenticateResult.Failure.Message);
var result = new ObjectResult(problemDetails)
{
ContentTypes = new MediaTypeCollection(),
StatusCode = problemDetails.Status,
DeclaredType = problemDetails.GetType()
};
await this.Executor.ExecuteAsync(actionContext, result);
}
}
}
For changing the body or Http status, you could try Context.Response.
Here is a demo code:
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace TestIdentity
{
public class CustomAuthenticationHandler<TOptions> : AuthenticationHandler<TOptions> where TOptions : AuthenticationSchemeOptions, new()
{
public CustomAuthenticationHandler(IOptionsMonitor<TOptions> options
, ILoggerFactory logger
, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
{
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
await Context.Response.WriteAsync("This is why you can't log in");
return AuthenticateResult.Fail("This is why you can't log in");
}
}
}
I created a form where the user can update his data account. In this form the user is also able to change the account password, before doing so, I ask him the current password, this is the field:
<div class="form-group">
<label>Current Password</label>
<input class="form-control" id="oldPassword"
asp-for="#Model.ExistingPassword" type="password" />
<div class="invalid-feedback"></div>
</div>
as you can see the oldPassword input bound the property ExistingPassword which is part of the ViewModel of that View and have the following declaration:
[Required, MinLength(6), MaxLength(50), DataType(DataType.Password)]
public string ExistingPassword { get; set; }
when the form is submitted I call the following ajax function:
$.post(url, user, function (response) {
//Some stuff
}).done(function (response) {
alert("Updated executed");
}).fail(function (jqXHR, textStatus, errorThrown) {
alert("Error happened!");
});
the parameter of the function are taken by the form, in particular:
url: $(this).attr('action');
user: $(this).serialize();
the action of the form will call the following controller: User\UpdateUser.
Inside the UpdateUser method I execute the following check:
public async Task<UserProfileViewModel> UpdateUserAsync(UserProfileViewModel updatedUser)
{
if (!await _userManager.CheckPasswordAsync(originalUser, updatedUser.ExistingPassword))
throw new Exception("Invalid password");
essentially, the condition check if the current password is correct, if not, then an exception will raised.
Now, my question with this is: how can I know which type of exception the method has generated?
I need to know which type of exception the method UpdateUser has generated because there are different exceptions in the method.
Suppose the Invalid Password exceptions is raised, I need to display a message inside invalid-feedback div, next to oldPassword, so the user know why the update has failed.
Thanks in advance for any help.
Normally, I recommend not using an exception except in actual exception circumstances, but given the way you've designed this, you have a few options.
I'd suggest creating a custom "UpdateUserException" that you can throw that will include additional information, which can be provided by an enum or just string.
public class UpdateUserException : Exception {
public UpdateUserError ErrorCondition;
public UpdateUserException(UpdateUserError error, string message)
{
ErrorCondition = error;
Message = message;
}
}
then you would throw it
throw new UpdateUserException(UpdateUserError.BadPassword, "Invalid Password");
then you would catch it
try {}
catch (UpdateUserException e)
{
if (e.ErrorCondition == UpdateUserException.BadPassword)
{
// handle your exception.
}
}
Have a look at the UserManager ChangePassword Method.
You can bind the UserManager to use DependencyInjection like this (in Startup.cs)
public async void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.UseAuthentication();
app.UseMvc();
var scopeFactory = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>();
using (var scope = scopeFactory.CreateScope())
{
UserManager<User> userManager = scope.ServiceProvider.GetRequiredService<UserManager<User>>();
}
}
And then in your Controller s Constructor
private readonly UserManager<User> _userManager;
public AccountController(UserManager<User> userManager)
{
_userManager = userManager;
}
And finally your endpoint:
[HttpPost("ChangePassword")]
public async Task<IActionResult> ChangePassword([FromBody]ChangePasswordRequest changePasswordParams)
{
if (changePasswordParams == null)
return BadRequest($"{nameof(changePasswordParams)} must not be null!");
if (string.IsNullOrWhiteSpace(changePasswordParams.OldPassword) || string.IsNullOrWhiteSpace(changePasswordParams.NewPassword))
return BadRequest("old and new passwords have to be provided, but they both are empty.");
var userId = User.Claims.FirstOrDefault(c => c.Type == "id")?.Value;
var user = await _userManager.FindByIdAsync(userId);
var result = await _userManager.ChangePasswordAsync(user, changePasswordParams.OldPassword, changePasswordParams.NewPassword);
if (result.Succeeded)
return NoContent();
return BadRequest(result.Errors);
}
after that you can handle the errors in a switch statement.
Using Exceptions for handled errors are not recommended since they generally ends up with Internal Server error and actually It is beyond of its purpose.
The best approach would be to send BadRequest as It is stated by #maerlin.
However, If you insist to use Exceptions in your application or your applciation is architected to work in this way. I suggest you to inherit new CustomApplcationException class from ApplicationException and then inherit UpdateUserException and vs. from CustomApplicationException class. After that, I Suggest you to handle your exceptions in ErrorHandlingMiddleware and return HandledExceptions at least with BadRequest (400) status code.
The Example Code would be
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILoggerManager _logger;
public ExceptionMiddleware(RequestDelegate next, ILoggerManager logger)
{
_logger = logger;
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext)
{
try
{
await _next(httpContext);
}
catch (CustomApplicationException cae)
{
await HandleCustomExceptionAsync(httpContext, cae);
}
catch (Exception ex)
{
_logger.LogError($"Something went wrong: {ex}");
await HandleExceptionAsync(httpContext, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
return context.Response.WriteAsync(new ErrorDetails()
{
StatusCode = context.Response.StatusCode,
Message = "Internal Server Error from the custom middleware."
}.ToString());
}
private static Task HandleCustomExceptionAsync(HttpContext context, Exception exception)
{
context.Response.StatusCode = 400;
return context.Response.WriteAsync(new ErrorDetails()
{
StatusCode = context.Response.StatusCode,
Message = exception.Message
}.ToString());
}
}
then you need to regiter middleware in your Startup.cs
app.UseMiddleware<ExceptionMiddleware>();
please see https://code-maze.com/global-error-handling-aspnetcore/ and http://www.talkingdotnet.com/global-exception-handling-in-aspnet-core-webapi/ for further details.