Randomly occurring issue: 'Unable to resolve service for type (...)' with Hangfire - .net-core

I encountered a strange issue with Hangfire. I'm getting sometimes (totally randomly) error about Unable to resolve service for type 'AW.Services.Interfaces.ISmsService' while attempting to activate 'AW.Services.Jobs.SendSmsJob'. When I click requeue on dashboard on this failed job it either fails again or finish with success. It happens really randomly and I don't have any idea what is happening.
I have registered my interface in IoC of course like this: services.AddTransient<ISmsService, SmsService>();.
I'm using the following packages versions:
.NET Core 3.1
Hangfire v1.7.9
Hangfire.AspNetCore v1.7.9
Hangfire.Console v1.4.2
Hangfire.PostgreSql v1.6.4.1
My hangfire's configuration in startup.cs is the following:
services.AddHangfire(config =>
{
config.UsePostgreSqlStorage(Configuration["AW_API_DB_CONNECTIONSTRING"]);
config.UseConsole();
});
GlobalConfiguration.Configuration.UseSerializerSettings
(
new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
}
);
(...)
app.UseHangfireServer(new BackgroundJobServerOptions
{
WorkerCount = backgroundProcessingWorkersCount
});
app.UseHangfireServer(new BackgroundJobServerOptions
{
WorkerCount = backgroundProcessingWorkersCount,
Queues = new[] { JobQueueTypes.Transactions }
});
app.UseHangfireDashboard("/dashboard", new DashboardOptions
{
Authorization = new[] { new AgriWalletDashboardAuthFilter() },
I
Below I've copied the entire log from hangfire's dashboard:
Unable to resolve service for type 'AW.Services.Interfaces.ISmsService' while attempting to activate 'AW.Services.Jobs.SendSmsJob'. System.InvalidOperationException: Unable to resolve service for type 'AW.Services.Interfaces.ISmsService' while attempting to activate 'AW.Services.Jobs.SendSmsJob'. at
Microsoft.Extensions.DependencyInjection.ActivatorUtilities.ConstructorMatcher.CreateInstance(IServiceProvider
provider) at
Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateInstance(IServiceProvider
provider, Type instanceType, Object[] parameters) at
Hangfire.Server.CoreBackgroundJobPerformer.Perform(PerformContext
context)
at Hangfire.Server.BackgroundJobPerformer.<>c__DisplayClass9_0.b__0()
at Hangfire.Server.BackgroundJobPerformer.InvokePerformFilter(IServerFilter
filter, PerformingContext preContext, Func1 continuation at
Hangfire.Server.BackgroundJobPerformer.InvokePerformFilter(IServerFilter
filter, PerformingContext preContext, Func1 continuation) at
Hangfire.Server.BackgroundJobPerformer.PerformJobWithFilters(PerformContext
context, IEnumerable`1 filters) at
Hangfire.Server.BackgroundJobPerformer.Perform(PerformContext context)
at Hangfire.Server.Worker.PerformJob(BackgroundProcessContext context,
IStorageConnection connection, String jobId)

Migration from netcore2 to netcore3 may cause issues with Dependency Injection. Please verify the project Program.cs and Startup.cs classes
https://learn.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-3.1&tabs=visual-studio

Related

What is causing my DbUpdateConcurrencyException?

In my .NET Core Web API, I have implemented the transactional outbox pattern to monitor a database table and publish messages to an Azure Service Bus topic whenever a record appears in the database table. This takes place within a hosted service class that inherits from Microsoft.Extensions.Hosting.BackgroundService. This is a stripped-down version of what I have:
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
IEnumerable<RelayMessage> messagesToSend = new List<RelayMessage>();
// _scopeFactory is an implementation of Microsoft.Extensions.DependencyInjection.IServiceScopeFactory:
using (var scope = _scopeFactory.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();
while (!stoppingToken.IsCancellationRequested)
{
messagesToSend = await dbContext.RelayMessage.ToListAsync();
foreach (var message in messagesToSend)
{
try
{
await SendMessageToAzureServiceBus(message);
dbContext.RelayMessage.Remove(message);
dbContext.SaveChanges();
}
catch (Exception ex)
{
Log.Error(ex, $"Could not send message with id {message.RelayMessageId}.");
}
}
await Task.Delay(5000, stoppingToken);
}
}
await Task.CompletedTask;
}
catch (Exception ex)
{
Log.Error(ex, "Exception thrown while processing messages.");
}
The records are being deleted from the database, but the following exception gets thrown on the call to SaveChanges():
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ThrowAggregateUpdateConcurrencyException(Int32 commandIndex, Int32 expectedRowsAffected, Int32 rowsAffected)
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeResultSetWithoutPropagation(Int32 commandIndex, RelationalDataReader reader)
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.Consume(RelationalDataReader reader)
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.Execute(IRelationalConnection connection)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(IEnumerable`1 commandBatches, IRelationalConnection connection)
at Microsoft.EntityFrameworkCore.Storage.RelationalDatabase.SaveChanges(IList`1 entries)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(IList`1 entriesToSave)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(DbContext _, Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges()
at ReinsuranceReferenceSystemApi.Services.ServiceBus.ParticipantPublishingService.ExecuteAsync(CancellationToken stoppingToken)
I did check out the link in the exception message, but am not sure if the information applies to my situation. The RelayMessage instance is created and saved to the database (in a method not shown here), then this method reads it and deletes it. There aren't any modifications of this type anywhere in the application, so I'm unclear on how this could be a concurrency issue.
I'd appreciate any help.
EDIT:
Here's the registration of my DbContext in Startup.cs:
services.AddDbContext<MyDbContext>(o =>
{
o.UseSqlServer(Configuration.GetConnectionString("MyConnectionString"));
});

Dotmim.Sync SqliteSyncProvider is throwing exception in Xamarin IOS | Offline Sync

I am trying to use Dotmim.Sync with my Xamarin IOS project.
I have added a .net standard 2.0 project in my solution. Added Dotmim.Sync.Sqlite & Dotmim.Sync.Web.Client nuggets.
Here is the SyncService code:
public async Task SyncAsync()
{
var proxyClientProvider = new WebClientOrchestrator("https://localhost:44358/api/sync");
var clientProvider = new SqliteSyncProvider("mymobile.db");
var progress = new SynchronousProgress<ProgressArgs>(s => Console.WriteLine($"{s.Context.SyncStage}:\t{s.Message}"));
var agent = new SyncAgent(clientProvider, proxyClientProvider);
do
{
// Launch the sync process
var s1 = await agent.SynchronizeAsync(progress);
// Write results
Console.WriteLine(s1);
} while (Console.ReadKey().Key != ConsoleKey.Escape);
Console.WriteLine("End");
}
I have added that .net standard project reference into my iOS project and called the Sync Service class (just for the POC).
mButton.TouchUpInside += (sender, e) =>
{
SyncService sync = new SyncService();
Sync. SyncAsync ();
};
at
var s1 = await agent.SynchronizeAsync(progress);
It's throwing an exception:
System.NullReferenceException: Object reference not set to an instance of an object
at SQLitePCL.raw.sqlite3_open_v2 (SQLitePCL.utf8z filename, SQLitePCL.sqlite3& db,
System.Int32 flags, SQLitePCL.utf8z vfs) [0x00000] in <15ecb38d58394d7b88b3f841a7dda078>:0
at SQLitePCL.raw.sqlite3_open_v2 (System.String filename, SQLitePCL.sqlite3& db,
System.Int32 flags, System.String vfs) [0x0000e] in <15ecb38d58394d7b88b3f841a7dda078>:0
At Microsoft. Data. Sqlite. SqliteConnection. Open () [0x00122] in
<9ffe4c48f3134a7b905b5da527410f26>:0
at System.Data.Common.DbConnection.OpenAsync (System.Threading.CancellationToken
cancellationToken) [0x00011] in /Library/Frameworks/Xamarin.iOS.framework/Versions/12.8.0.0/src/Xamarin.iOS/external/corefx/src/. System.Data.Common/src/System/Data/Common/DbConnection.cs:122
--- End of stack trace from previous location where exception was thrown ---
And System.Console.ReadKey gives 'Operation is not supported on this platform.'
Any suggestion/help is welcome.
You should use SQLitePCL.Batteries_V2.Init();
This is part of the Xamarin limitations. More information here : https://learn.microsoft.com/en-us/dotnet/standard/data/sqlite/xamarin
Regarding Dotmim.Sync, see more informaition here : https://github.com/Mimetis/Dotmim.Sync/issues/249#issuecomment-609025301

Changing Request Path in .Net Core 3.1

Prior to 3.0, I could change the path of a request (without any form of browser redirection) by just accessing the HttpRequest property of the HttpContext and then changed the value of the Path.
As an example, to display a page for a user who needed to change his/her password (irrespective of the page the user intended to visit), I extended the HttpContext
public static void ChangeDefaultPassword(this HttpContext context)
=> context.Request.Path = "/Account/ChangePassword";
This piece of code takes the user to the action method ChangePassword in the AccountController without executing the action method the user intends to visit.
Then enters dotnet core 3.1.
In 3.1, the extension method changes the path. However, it never executes the action method. It ignores the updated path.
I am aware this is due to the changes in the routing.The endpoint can now be accessed with the extension method HttpContext.GetEndpoint(). There is also an extension method HttpContext.SetEndpoint which seems to be the right way to set a new endpoint. However, there is no sample of how to accomplish this.
The Question
How do I change the request path, without executing the original path?
What I Have Tried
I tried changing the path. It seems routing in dotnet core 3.1 ignores the value of the HttpRequest path value.
I tried redirecting with context.Response.Redirect("/Account/ChangePassword");. This worked but it first executed the original action method requested by the user. This behavior defeated the purpose.
I tried using the extension method HttpContext.SetEndpoint, but there was no example available to work with.
The way I worked around this issue is to use EndpointDataSource directly, which is a singleton service that is available from DI as long as you have the routing services registered. It works as long as you can provide the controller name and the action name, which you can specify at compile time. This negates the need to use IActionDescriptorCollectionProvider or build the endpoint object or request delegate by yourself (which is pretty complicated...):
public static void RerouteToActionMethod(this HttpContext context, EndpointDataSource endpointDataSource, string controllerName, string actionName)
{
var endpoint = endpointDataSource.Endpoints.FirstOrDefault(e =>
{
var descriptor = e.Metadata.GetMetadata<ControllerActionDescriptor>();
// you can add more constraints if you wish, e.g. based on HTTP method, etc
return descriptor != null
&& actionName.Equals(descriptor.ActionName, StringComparison.OrdinalIgnoreCase)
&& controllerName.Equals(descriptor.ControllerName, StringComparison.OrdinalIgnoreCase);
});
if (endpoint == null)
{
throw new Exception("No valid endpoint found.");
}
context.SetEndpoint(endpoint);
}
I was able to find a working solution. My solution works by manually setting a new endpoint with the SetEndpoint extension method.
Here is an extension method I created to resolve this issue.
private static void RedirectToPath(this HttpContext context, string controllerName, string actionName )
{
// Get the old endpoint to extract the RequestDelegate
var currentEndpoint = context.GetEndpoint();
// Get access to the action descriptor collection
var actionDescriptorsProvider =
context.RequestServices.GetRequiredService<IActionDescriptorCollectionProvider>();
// Get the controller aqction with the action name and the controller name.
// You should be redirecting to a GET action method anyways. Anyone can provide a better way of achieving this.
var controllerActionDescriptor = actionDescriptorsProvider.ActionDescriptors.Items
.Where(s => s is ControllerActionDescriptor bb
&& bb.ActionName == actionName
&& bb.ControllerName == controllerName
&& (bb.ActionConstraints == null
|| (bb.ActionConstraints != null
&& bb.ActionConstraints.Any(x => x is HttpMethodActionConstraint cc
&& cc.HttpMethods.Contains(HttpMethods.Get)))))
.Select(s => s as ControllerActionDescriptor)
.FirstOrDefault();
if (controllerActionDescriptor is null) throw new Exception($"You were supposed to be redirected to {actionName} but the action descriptor could not be found.");
// Create a new route endpoint
// The route pattern is not needed but MUST be present.
var routeEndpoint = new RouteEndpoint(currentEndpoint.RequestDelegate, RoutePatternFactory.Parse(""), 1, new EndpointMetadataCollection(new object[] { controllerActionDescriptor }), controllerActionDescriptor.DisplayName);
// set the new endpoint. You are assured that the previous endpoint will never execute.
context.SetEndpoint(routeEndpoint);
}
Important
You must make the view of the action method available by placing it in the Shared folder. Alternatively, you may decide to provide a custom implementation of IViewLocationExpander
Before accessing the endpoint, the routing middleware must have executed.
USAGE
public static void ChangeDefaultPassword(this HttpContext context)
=> context.RedirectToPath("Account","ChangePassword");
Check your middleware order.
The middleware exposed by .UseRouting() is what's responsible for deciding which endpoint to hit based on the incoming request path. If your path rewrite middleware comes later in the pipeline (like mine was), it'll be too late and the routing decision has been made.
Moving my custom middleware before UseRouting() ensured that the path was set as I needed it before the routing middleware had been hit.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, TelemetryConfiguration telemetryConfig)
{
//snip
app.UseMiddleware<PathRewritingMiddleware>();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
//snip
}
I had a similar reroute issue. In my case, I want to reroute users to a "you don't have permissions" view when an AuthorationHandler fails. I applied the following code, notably (httpContext.Response.Redirect(...)) in (.Net Core 3.1) to route me to a NoPermissions action on a Home Controller.
In the handler class:
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, FooBarRequirement requirement) {
var hasAccess = await requirement.CheckAccess(context.User);
if (hasAccess)
context.Succeed(requirement);
else {
var message = "You do not have access to this Foobar function";
AuthorizeHandler.NoPermission(mHttpContextAccessor.HttpContext, context, requirement, message);
}
}
I wrote a static class to handle the redirect, passing in the url expected by the controller and action plus an error message, and the redirect permanent flag set to true:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
namespace Foo.BusinessLogic.Security {
public static class AuthorizeHandler {
public static void NoPermission(HttpContext httpContext,
AuthorizationHandlerContext context, IAuthorizationRequirement requirement, string
errorMessage) {
context.Succeed(requirement);
httpContext.Response.Redirect($"/home/nopermission/?m={errorMessage}", true);
}
}
}
Finally, the controller and action that handles the view and message
[AllowAnonymous]
public IActionResult NoPermission(string m) {
return View("NoPermission", m);
}
}
In my case, I am manually selecting the matching endpoint in a DynamicRouteValueTransformer. I have a mostly working solution but have to switch to other priorities. Perhaps someone else can create a more elegant solution using built in Action executors.
RequestDelegate requestDelegate = async (HttpContext x) =>
{//manually handle controller activation, method invocation, and result processing
var actionContext = new ActionContext(x, new RouteData(values), new ControllerActionDescriptor() { ControllerTypeInfo = controllerType.GetTypeInfo() });
var activator = x.RequestServices.GetService(typeof(IControllerActivator)) as ServiceBasedControllerActivator;
var controller = activator.Create(new ControllerContext(actionContext));
var arguments = methodInfo.GetParameters().Select(p =>
{
object r;
if (requestData.TryGetValue(p.Name, out object value)) r = value;
else if (p.ParameterType.IsValueType) r = Activator.CreateInstance(p.ParameterType);
else r = null;
return r;
});
var actionResultTask = methodInfo.Invoke(controller, arguments.ToArray());
var actionTask = actionResultTask as Task<IActionResult>;
if (actionTask != null)
{
var actionResult = await actionTask;
await actionResult.ExecuteResultAsync(actionContext);//errors here. actionContext is incomplete
}
};
var endpoint = new Endpoint(requestDelegate, EndpointMetadataCollection.Empty, methodInfo.Name);
httpContext.SetEndpoint(endpoint);

MoveNext error using async with Xamarin forms

I have a Xamarin Android forms project using a CodeIgniter back end, with NuSoap.
I visual studio I created a .NET core project for testing, added a connected service to the server. Created a async task to pull the data from the server, this all worked correctly.
var client = new TbqService.ServicePortTypeClient();
var loginTask = Task.Run(() => client.logInAsync("user", "password"));
echoTask.Wait();
Console.WriteLine($"Login result {loginTask.Result}");
I then followed the same sequence for the Xamarin forms application but am getting the following error. I have seen comments about setting the SSL to TLS 1.2 and removing the bin/obj folder and rebuilding. Neither helped.
{System.NullReferenceException: Object reference not set to an instance of an object.
at MyThingApp.Models.DataConnect+<Login>d__13.MoveNext () [0x00023] in
D:\WebSites\TheMyThing_Projects\MyThingApp\MyThingApp\MyThingApp\Models\DataConnect.cs:31 }
Is there a different in the way the two work, should I be handling them differently?
public async Task<bool> Login(string email, string password)
{
try
{
var c = new TbqService.ServicePortTypeClient();
var result = await c.logInAsync(email, password); // line 31 in error
return result.Contains("true");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex);
}
return false;
}
It seems like result might be returning null. And so it crashes when you are trying to access contents inside result. Add a null check, and that should remove you error.
if (result != null)
return result.Contains("true");

App Service to EntityFramework using MSI

I'm trying to retrofit MSI to an existing app.
The original app's DbContext used only a Constructor that found a ConnectionString by the same name in the web.config.
I've modified it to use a DbConnectionFactory to inject an AccessToken.
public class AppCoreDbContext : DbContext {
public AppCoreDbContext() : this("AppCoreDbContext")
{
}
public AppCoreDbContext(string connectionStringOrName) : base( OpenDbConnectionBuilder.Create(connectionStringOrName).Result, true)
{
}
...etc...
}
The class that it is invoking looks like:
public static class OpenDbConnectionBuilder
{
public static async Task<DbConnection> CreateAsync(string connectionStringName)
{
var connectionStringSettings = ConfigurationManager.ConnectionStrings[connectionStringName];
var dbConnection = DbProviderFactories
.GetFactory(connectionStringSettings.ProviderName)
.CreateConnection();
dbConnection.ConnectionString = connectionStringSettings.ConnectionString;
await AttachAccessTokenToDbConnection(dbConnection);
// Think DbContext will open it when first used.
//await dbConnection.OpenAsync();
return dbConnection;
}
static async Task AttachAccessTokenToDbConnection(IDbConnection dbConnection)
{
SqlConnection sqlConnection = dbConnection as SqlConnection;
if (sqlConnection == null)
{
return;
}
string msiEndpoint = Environment.GetEnvironmentVariable("MSI_ENDPOINT");
if (string.IsNullOrEmpty(msiEndpoint))
{
return;
}
var msiSecret = Environment.GetEnvironmentVariable("MSI_SECRET");
if (string.IsNullOrEmpty(msiSecret))
{
return;
}
string accessToken = await AppCoreDbContextMSITokenFactory.GetAzureSqlResourceTokenAsync();
sqlConnection.AccessToken = accessToken;
}
}
Which invokes
// Refer to: https://winterdom.com/2017/10/19/azure-sql-auth-with-msi
public static class AppCoreDbContextMSITokenFactory
{
private const String azureSqlResource = "https://database.windows.net/";
public static async Task<String> GetAzureSqlResourceTokenAsync()
{
var provider = new AzureServiceTokenProvider();
var result = await provider.GetAccessTokenAsync(azureSqlResource);
return result;
}
}
The result of the above is that when tracking it with a debugger, it gets to
var result = await provider.GetAccessTokenAsync(azureSqlResource);
then hangs for ever.
Note: I'm working on a personal machine, not joined to the organisation domain -- but my personal MSA has been invited to the organisation's domain.
Admittedly, I've taken a hiatus from development for a couple of years, and the hang is probably due to having made a mistake around await (always been rough on understanding that implicitly)... but while trying to figure that out, and the documentation is pretty sparse, would appreciate feedback as to whether the above was the intended approach for using MSI.
I'm wondering:
When deploying to Azure, we can tell the ARM to create the Identity -- when developing, how do we tell the local machine to use MSI?
If on the dev machine the connection string is to a local db, and I create and add the token anyway, will it ignore it, or raise an exception.
This is a bit beyond the scope of discussing MSI, but I've never before created a dbConnection to use within a DbContext. Does anyone know the pros/cons of the DbContext 'owning' the connection? I'm assuming that it would be wiser to own & close the connection when the dbcontext is closed.
Basically...this is all new, so would appreciate any advice on getting this working -- the concept of being able to deploy without secrets would be awesome and would really like to get this demo working.
Thanks very much!
Hello user9314395: Managed Service Identity only works with resources running on Azure. While we don't support the local development scenario, you might consider looking into using the following (preview) library: https://learn.microsoft.com/en-us/azure/key-vault/service-to-service-authentication

Resources