我试图从服务中调用存储库中的方法.
上下文、存储库和服务都被定义为范围服务.
这是我首先调用的方法:
public async void ReceiveWebhook(HttpContext httpContext)
{
// some unimportant checks here
var productPurchaseRequest = new ProductPurchaseRequest
{
Amount = Convert.ToInt32(result?.Quantity),
Timestamp = DateTime.Now,
ProductType = productType,
PaymentProviderOrderId = Convert.ToInt32(result?.OrderId),
PaymentProviderProductId = Convert.ToInt32(result?.ProductId),
PaymentProviderTransactionId = result?.TransactionId!,
PaymentModel = PaymentModel.Subscription,
PhoneNumber = result?.Passthrough!
//todo: change payment model
};
var bought = await _productProvisioningRepository.PurchaseProduct(productPurchaseRequest);
}
这是存储库中的方法:PurchaseProduct()
:
public async Task<bool> PurchaseProduct(ProductPurchaseRequest productPurchaseRequest)
{
await using var transactionScope = await _context.Database.BeginTransactionAsync();
var query = from u in _context.signumid_user
where u.PhoneNumber == productPurchaseRequest.PhoneNumber
select u;
var user = await query.FirstOrDefaultAsync();
if (user == null)
{
return false;
//todo: log user with phone number does not exist
}
try
{
var transaction = new Transaction
{
Timestamp = productPurchaseRequest.Timestamp,
PaymentProviderOrderId = productPurchaseRequest.PaymentProviderOrderId,
PaymentProviderProductId = productPurchaseRequest.PaymentProviderProductId,
PaymentProviderTransactionId = productPurchaseRequest.PaymentProviderTransactionId
};
var transactionDb = await _context.signumid_transaction.AddAsync(transaction);
await _context.SaveChangesAsync();
var productEntry = new ProductEntry
{
Amount = productPurchaseRequest.Amount,
ExpiryDate = productPurchaseRequest.Timestamp.AddMonths(1),
PaymentModel = (int) PaymentModel.Subscription,
ProductType = productPurchaseRequest.ProductType,
UserId = 1
};
var productEntryDb = await _context.signumid_product_entry.AddAsync(productEntry);
await _context.SaveChangesAsync();
var transactionProductEntry = new Transaction_ProductEntry
{
TransactionId = transactionDb.Entity.Id,
ProductEntryId = productEntryDb.Entity.Id
};
var transactionProductEntryDb = await _context.sisgnumid_transaction_product_entry.AddAsync(transactionProductEntry);
await _context.SaveChangesAsync();
//todo: check if everything is okay with database entries
await transactionScope.CommitAsync();
return true;
}
catch (Exception e)
{
// todo: add log
Console.WriteLine(e);
await transactionScope.RollbackAsync();
return false;
}
}
这是节目.cs文件:
using System.Diagnostics;
using System.Reflection;
using System.Text.Json.Serialization;
using FluentMigrator.Runner;
using Hangfire;
using Hangfire.PostgreSql;
using Hangfire.SQLite;
using Hangfire.SqlServer;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Http.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.DataEncryption;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Newtonsoft.Json;
using Serilog;
using Serilog.Exceptions;
using Serilog.Exceptions.Core;
using Signumid.ExceptionMiddleware;
using Signumid.Global;
using Signumid.MigratorRunner;
using Signumid.ProductProvisioning;
using Signumid.ProductProvisioning.Migrations;
var builder = WebApplication.CreateBuilder(args);
var configurationBuilder = new ConfigurationBuilder()
.SetBasePath(builder.Environment.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
var configurationRoot = configurationBuilder.Build();
ConfigureLogging(configurationRoot);
var applicationSettings = new Signumid.ApplicationSettings.ApplicationSettings();
configurationRoot.Bind(applicationSettings);
SignumIdGlobal.InitialiseApplicationSettings(applicationSettings);
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.Configure<JsonOptions>(options =>
{
options.SerializerOptions.PropertyNameCaseInsensitive = false;
options.SerializerOptions.PropertyNamingPolicy = null;
options.SerializerOptions.WriteIndented = true;
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
});
MigratorRunner.MigrateDatabase(builder.Services,
SignumIdGlobal.ApplicationSettings.ProductProvisioningApplicationSettings.ConnectionString,
SignumIdGlobal.ApplicationSettings.ProductProvisioningApplicationSettings.DatabaseProvider,
Assembly.GetAssembly(typeof(InitialMigration)));
builder.Services.AddScoped<ProductProvisioningContext>();
builder.Services.AddScoped<ProductProvisioningRepository>();
builder.Services.AddScoped<ProductProvisioningService>();
ConfigureHangfire(builder.Services);
ConfigureHealthCheck(builder.Services);
builder.Services.AddCors(options =>
options.AddPolicy("CorsPolicy",
corsPolicyBuilder =>
{
corsPolicyBuilder
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
.SetIsOriginAllowed(_ => true)
.WithExposedHeaders("Content-Disposition", "Content-Length");
}));
builder.Services.AddAuthorization();
var app = builder.Build();
// Configure hangfire to use the new JobActivator we defined.
// Use the previously configured CorsPolicy policy
app.UseCors("CorsPolicy");
app.ConfigureExceptionHandler();
// Configure the HTTP request pipeline.
app.UseSwagger();
app.UseSwaggerUI();
app.UseHttpsRedirection();
app.MapHealthChecks("/v/1/health/basic");
app.MapHealthChecks("/v/1/health/simplified",
new HealthCheckOptions
{
ResponseWriter = async (context, report) =>
{
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(new
{
status = report.Status.ToString(),
monitors = report.Entries.Select(e => new
{key = e.Key, value = Enum.GetName(typeof(HealthStatus), e.Value.Status)})
}));
}
}
).RequireHost(SignumIdGlobal.ApplicationSettings.ProductProvisioningApplicationSettings.HealthCheckHosts);
app.MapGet("/v/1/signer/getAvailableSignatures",
(string phoneNumber, int productType, ProductProvisioningService service) => service.RetrieveRemainingProductAmountForUser(phoneNumber, productType));
app.MapGet("/v/1/signer/generatePayLink",
(string phoneNumber, int quantity, int productId, ProductProvisioningService service) => service.GeneratePayLink(phoneNumber, quantity, productId));
app.MapPost("/v/1/signer/receiveWebhook",
(HttpContext context, ProductProvisioningService service) => service.ReceiveWebhook(context));
app.UseAuthorization();
app.UseHangfireDashboard();
GlobalConfiguration.Configuration
.UseActivator(new HangfireActivator(app.Services));
var runner = app.Services.GetRequiredService<IMigrationRunner>();
// Execute the migrations
runner.MigrateUp();
app.Run();
static void ConfigureLogging(IConfigurationRoot configuration)
{
try
{
Serilog.Debugging.SelfLog.Enable(msg => Debug.WriteLine(msg));
Serilog.Debugging.SelfLog.Enable(Console.Error);
// Impossible to set with appsettings:
// https://stackoverflow.com/questions/58587661/json-configuration-for-serilog-exception-destructurers/58622735#58622735
// https://github.com/RehanSaeed/Serilog.Exceptions/issues/58
var loggingConfiguration = new LoggerConfiguration()
.Enrich
.WithExceptionDetails(new DestructuringOptionsBuilder().WithDefaultDestructurers())
.ReadFrom
.Configuration(configuration);
Log.Logger = loggingConfiguration
.CreateLogger();
}
catch (Exception e)
{
Serilog.Debugging.SelfLog.Enable(msg => Debug.WriteLine(msg));
Serilog.Debugging.SelfLog.Enable(Console.Error);
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.MinimumLevel.Debug() // set to minimal in serilog
.CreateLogger();
Log.Debug(e,
"Unable to import serilog configuration from appsettings.json, logging only to console. Error: {@Ex}", e);
}
var currentDomain = AppDomain.CurrentDomain;
currentDomain.UnhandledException += UnhandledExceptionHandler;
}
static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args)
{
var e = (Exception) args.ExceptionObject;
Log.Fatal(e, "Unhandled exception caught : {@error}", e);
Log.Fatal("Runtime terminating: {0}", args.IsTerminating);
}
static void ConfigureHealthCheck(IServiceCollection services)
{
if (SignumIdGlobal.ApplicationSettings.ProductProvisioningApplicationSettings.DatabaseProvider.Equals("SqlLite",
StringComparison.InvariantCultureIgnoreCase))
{
services.AddHealthChecks()
.AddSqlite(SignumIdGlobal.ApplicationSettings.ProductProvisioningApplicationSettings.ConnectionString);
}
else if (SignumIdGlobal.ApplicationSettings.ProductProvisioningApplicationSettings.DatabaseProvider.Equals(
"postgresql",
StringComparison.InvariantCultureIgnoreCase))
{
services.AddHealthChecks()
.AddNpgSql(SignumIdGlobal.ApplicationSettings.ProductProvisioningApplicationSettings.ConnectionString);
}
else if (SignumIdGlobal.ApplicationSettings.ProductProvisioningApplicationSettings.DatabaseProvider.Equals(
"sqlserver",
StringComparison.InvariantCultureIgnoreCase))
{
services.AddHealthChecks()
.AddSqlServer(SignumIdGlobal.ApplicationSettings.ProductProvisioningApplicationSettings.ConnectionString);
}
else
{
services.AddHealthChecks();
}
}
static void ConfigureHangfire(IServiceCollection services)
{
services.AddHangfire(configuration =>
{
configuration
.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings();
});
if (SignumIdGlobal.ApplicationSettings.ProductProvisioningApplicationSettings.DatabaseProvider.Equals("SqlLite",
StringComparison.InvariantCultureIgnoreCase))
{
GlobalConfiguration.Configuration.UseSQLiteStorage(SignumIdGlobal.ApplicationSettings
.ProductProvisioningApplicationSettings
.ConnectionString, new SQLiteStorageOptions
{
SchemaName = "product_provisioning"
});
}
else if (SignumIdGlobal.ApplicationSettings.ProductProvisioningApplicationSettings.DatabaseProvider.Equals(
"postgresql",
StringComparison.InvariantCultureIgnoreCase))
{
GlobalConfiguration.Configuration.UsePostgreSqlStorage(
SignumIdGlobal.ApplicationSettings.ProductProvisioningApplicationSettings.ConnectionString,
new PostgreSqlStorageOptions
{
SchemaName = "product_provisioning"
});
}
else if (SignumIdGlobal.ApplicationSettings.ProductProvisioningApplicationSettings.DatabaseProvider.Equals(
"sqlserver",
StringComparison.InvariantCultureIgnoreCase))
{
GlobalConfiguration.Configuration.UseSqlServerStorage(
SignumIdGlobal.ApplicationSettings.ProductProvisioningApplicationSettings.ConnectionString,
new SqlServerStorageOptions
{
SchemaName = "product_provisioning"
});
}
services.AddHangfireServer();
}
当我声明事务范围时,方法的第一行出现了一个错误.
无法访问已释放的上下文实例.此错误的一个常见原因是,处理通过依赖项注入解析的上下文实例,然后在应用程序的其他地方try 使用相同的上下文实例.如果对上下文实例调用"Dispose",或将其包装到using语句中,可能会发生这种情况.如果您使用的是依赖项注入,那么应该让依赖项注入容器处理上下文实例.