1 “System.InvalidOperationException: The ConnectionString property has not been initialized.”异常
1.1 异常产生原因:
一般情况下启动项中存在appsettings.json文件,且该文件中持久化存储着指定数据库软件指定数据库的连接设定数据,当前程序启动时已经完成了EntityFrameworkCore中间件的实例化,所有不会产生上述异常。但是如果启动项中不存在appsettings.json文件,且定数据库软件指定数据库的连接设定数据是通过前端页面设定时,如果在控制器类的构造方法中直接使用EntityFrameworkCore中间件实例时,这里就会由于连接字符串参数实例的缺失,直接使用构建方法中的EntityFrameworkCore中间件实例就会出现上述异常。
2 Core.Infrastructure.IEngine
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Core.Infrastructure
{
/// <summary>
/// 【引擎--接口】
/// <remarks>
/// 摘要:
/// 继承于该接口的具体要求实现类实现了以下功能:
/// 1、“Engine”(引擎)实例用于对.NetCore框架内置依赖注入容器接口实例所存储的当前程序中的具体现类和中间件的实例进行集中管理。
/// 2、“Engine”(引擎)实例用于对当前程序中的自定义管道中间件实例进行集中管理。
/// 3、“Engine”(引擎)实例用于对服务提供程序接口进行实例化,通过该实例的“GetRequiredService”或“GetService”方法从内置依赖注入容器中获取1个指定接口/类的1/n实例(注意:1个指定接口/类的实例必须已经注入到了内置依赖注入容器中)。
/// 4、“Engine”(引擎)实例是单例的,即在程序运行过程中“Engine”实例是唯一的,该实例,在运行过程序中只能被替换(同内存的1个引用地址),而不能再次被实例化1次(两个不内存的引用地址)。
/// </remarks>
/// </summary>
public interface IEngine
{
#region 方法
/// <param name="services">.NetCore框架内置依赖注入容器接口实例。</param>
/// <param name="configuration">.NetCore框架内置配置接口实例(存储着当前程序中所有*.json文件中的数据)。</param>
/// <summary>
/// 【配置服务】
/// <remarks>
/// 摘要:
/// 把具体现类和中间件注入到内置依赖注入容器后,把“Engine”实例存储到单例实例的字典成员实例中,同时把.NetCore框架内置依赖注入容器接口实例所存储的当前程序中的具体现类和中间件的实例通过“Engine”(引擎)单例实例存储到单例类的字典属性成员实例中。
/// 说明:
/// 1、“Engine”实例用于对.NetCore框架内置依赖注入容器接口实例所存储的当前程序中的具体现类和中间件的实例进行集中管理。
/// 2、“Engine”实例是单例的,即在程序运行过程中“Engine”实例是唯一的,该实例,在运行过程序中只能被替换(同内存的1个引用地址),而不能再次被实例化1次(两个不内存的引用地址)。
/// </remarks>
/// </summary>
void ConfigureServices(IServiceCollection services, IConfiguration configuration);
/// <param name="application">.NetCore框架内置管道接口实例。</param>
/// <summary>
/// 【配置】
/// <remarks>
/// 摘要:
/// 通过.NetCore框架的内置管道接口实例,实例化“IServiceProvider”接口实例,同时把继承于“IStartup”所有具体现类中的中间件实例集成到.NetCore框架内置管道中。
/// </remarks>
/// </summary>
void ConfigureRequestPipeline(IApplicationBuilder application);
/// <typeparam name = "T"> 泛型类型实例(1个指定类/接口的类型实例)。</typeparam>
/// <param name="scope">作用域服务接口实例,用于从内置依赖注入容器中获取生命周期被注册为“AddScoped”的具体实现类的实例,默认值:null,即在默认状态下只能获取生命周期被注册为“AddSingleton/AddTransient”的具体实现类的实例。</param>
/// <summary>
/// 【解析】
/// <remarks>
/// 摘要:
/// 通过“GetService”方法从内置依赖注入容器中获取1个指定接口/类的1实例(注意:1个指定接口/类的实例必须已经注入到了内置依赖注入容器中)。
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定接口/类的1实例。
/// </returns>
/// </summary>
T Resolve<T>(IServiceScope scope = null) where T : class;
/// <param name="type">1个指定类/接口的类型实例</param>
/// <param name="scope">作用域服务接口实例,用于从内置依赖注入容器中获取生命周期被注册为“AddScoped”的具体实现类的实例,默认值:null,即在默认状态下只能获取生命周期被注册为“AddSingleton/AddTransient”的具体实现类的实例。</param>
/// <summary>
/// 【解析】
/// <remarks>
/// 摘要:
/// 通过“GetService”方法从内置依赖注入容器中获取1个指定接口/类的1实例(注意:1个指定接口/类的实例必须已经注入到了内置依赖注入容器中)。
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定接口/类的1实例。
/// </returns>
/// </summary>
object Resolve(Type type, IServiceScope scope = null);
/// <typeparam name = "T"> 泛型类型实例(1个指定类/接口的类型实例)。</typeparam>
/// <summary>
/// 【解析全部】
/// <remarks>
/// 摘要:
/// 通过“GetService”方法从内置依赖注入容器中获取1个指定接口/类的1/n实例(注意:1个指定接口/类的实例必须已经注入到了内置依赖注入容器中),把并这些实例存储到可枚举实例中。
/// </remarks>
/// <returns>
/// 返回:
/// 可枚举实例。该实例存储着1个指定接口/类的1/n实例。
/// </returns>
/// </summary>
IEnumerable<T> ResolveAll<T>();
/// <param name="type">1个指定的类/接口的类型实例。</param>
/// <summary>
/// 【未注入解析】
/// <remarks>
/// 摘要:
/// 如果1指定的类/接口未注入到内置依赖注入容器中,通过该方法以反射方式获取1指定的类/接口的1/n个实例。
/// </remarks>
/// <returns>
/// 返回:
/// 1指定的类/接口的1/n个实例。
/// </returns>
/// </summary>
object ResolveUnregistered(Type type);
#endregion
}
}
3 Core.Infrastructure.Engine
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Core.Infrastructure
{
/// <summary>
/// 【引擎--类】
/// <remarks>
/// 摘要:
/// 1、“Engine”(引擎)实例用于对.NetCore框架内置依赖注入容器接口实例所存储的当前程序中的具体现类和中间件的实例进行集中管理。
/// 2、“Engine”(引擎)实例用于对当前程序中的自定义管道中间件实例进行集中管理。
/// 3、“Engine”(引擎)实例用于对服务提供程序接口进行实例化,通过该实例的“GetRequiredService”或“GetService”方法从内置依赖注入容器中获取1个指定接口/类的1/n实例(注意:1个指定接口/类的实例必须已经注入到了内置依赖注入容器中)。
/// 4、“Engine”(引擎)实例是单例的,即在程序运行过程中“Engine”实例是唯一的,该实例,在运行过程序中只能被替换(同内存的1个引用地址),而不能再次被实例化1次(两个不内存的引用地址)。
/// </remarks>
/// </summary>
public class Engine : IEngine
{
#region 属性
/// <summary>
/// 【服务提供程序】
/// <remarks>
/// 摘要:
/// 服务提供程序接口实例,通过该实例的“GetRequiredService”或“GetService”方法从内置依赖注入容器中获取1个指定接口/类的1/n实例(注意:1个指定接口/类的实例必须已经注入到了内置依赖注入容器中)。
/// </remarks>
/// </summary>
public virtual IServiceProvider ServiceProvider { get; protected set; }
#endregion
#region 方法--私有/保护
/// <param name="scope">作用域服务接口实例,用于从内置依赖注入容器中获取生命周期被注册为“AddScoped”的具体实现类的实例,默认值:null,即在默认状态下只获取取生命周期被注册为“AddSingleton/AddTransient”的具体实现类的实例。</param>
/// <summary>
/// 【获取服务提供程序】
/// <remarks>
/// 摘要:
/// 获取服务提供程序接口实例,通过该实例的“GetRequiredService”或“GetService”方法从内置依赖注入容器中获取1个指定接口/类的1/n实例(注意:1个指定接口/类的实例必须已经注入到了内置依赖注入容器中)。
/// </remarks>
/// <returns>
/// 返回:
/// 服务提供程序接口实例,为获取具体实现类的实例提供支撑。
/// </returns>
/// </summary>
protected virtual IServiceProvider GetServiceProvider(IServiceScope scope = null)
{
if (scope == null)
{
var accessor = ServiceProvider?.GetService<IHttpContextAccessor>();
var context = accessor?.HttpContext;
//如果作用域服务接口实例存在,则返回“AddScoped”服务提供程序;如果作用域服务接口实例不存在,则返回生命周期被注册为“AddSingleton/AddTransient”的服务提供程序。
return context?.RequestServices ?? ServiceProvider;
}
return scope.ServiceProvider;
}
#endregion
#region 方法--接口实现
/// <param name="services">.NetCore框架内置依赖注入容器接口实例。</param>
/// <param name="configuration">.NetCore框架内置配置接口实例(存储着当前程序中所有*.json文件中的数据)。</param>
/// <summary>
/// 【配置服务】
/// <remarks>
/// 摘要:
/// 把具体现类和中间件注入到内置依赖注入容器后,把“Engine”实例存储到单例实例的字典成员实例中,同时把.NetCore框架内置依赖注入容器接口实例所存储的当前程序中的具体现类和中间件的实例通过“Engine”(引擎)单例实例存储到单例类的字典属性成员实例中。
/// 说明:
/// 1、“Engine”实例用于对.NetCore框架内置依赖注入容器接口实例所存储的当前程序中的具体现类和中间件的实例进行集中管理。
/// 2、“Engine”实例是单例的,即在程序运行过程中“Engine”实例是唯一的,该实例,在运行过程序中只能被替换(同内存的1个引用地址),而不能再次被实例化1次(两个不内存的引用地址)。
/// </remarks>
/// </summary>
public virtual void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
//把“Engine”实例存储到单例实例的字典成员实例中。
services.AddSingleton<IEngine>(this);
services.AddSingleton(services);
}
/// <param name="application">.NetCore框架内置管道接口实例。</param>
/// <summary>
/// 【配置】
/// <remarks>
/// 摘要:
/// 通过.NetCore框架的内置管道接口实例,实例化“IServiceProvider”接口实例,同时把继承于“IStartup”所有具体现类中的中间件实例集成到.NetCore框架内置管道中。
/// </remarks>
/// </summary>
public virtual void ConfigureRequestPipeline(IApplicationBuilder application)
{
//通过.NetCore框架的内置管道接口实例,实例化“IServiceProvider”接口实例。
ServiceProvider = application.ApplicationServices;
}
/// <typeparam name = "T"> 泛型类型实例(1个指定类/接口的类型实例)。</typeparam>
/// <param name="scope">作用域服务接口实例,用于从内置依赖注入容器中获取生命周期被注册为“AddScoped”的具体实现类的实例,默认值:null,即在默认状态下只能获取生命周期被注册为“AddSingleton/AddTransient”的具体实现类的实例。</param>
/// <summary>
/// 【解析】
/// <remarks>
/// 摘要:
/// 通过“GetService”方法从内置依赖注入容器中获取1个指定接口/类的1实例(注意:1个指定接口/类的实例必须已经注入到了内置依赖注入容器中)。
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定接口/类的1实例。
/// </returns>
/// </summary>
public virtual T Resolve<T>(IServiceScope scope = null) where T : class
{
return (T)Resolve(typeof(T), scope);
}
/// <param name="type">1个指定类/接口的类型实例</param>
/// <param name="scope">作用域服务接口实例,用于从内置依赖注入容器中获取生命周期被注册为“AddScoped”的具体实现类的实例,默认值:null,即在默认状态下只能获取生命周期被注册为“AddSingleton/AddTransient”的具体实现类的实例。</param>
/// <summary>
/// 【解析】
/// <remarks>
/// 摘要:
/// 通过“GetService”方法从内置依赖注入容器中获取1个指定接口/类的1实例(注意:1个指定接口/类的实例必须已经注入到了内置依赖注入容器中)。
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定接口/类的1实例。
/// </returns>
/// </summary>
public virtual object Resolve(Type type, IServiceScope scope = null)
{
return GetServiceProvider(scope)?.GetService(type);
}
/// <typeparam name = "T"> 泛型类型实例(1个指定类/接口的类型实例)。</typeparam>
/// <summary>
/// 【解析全部】
/// <remarks>
/// 摘要:
/// 通过“GetService”方法从内置依赖注入容器中获取1个指定接口/类的1/n实例(注意:1个指定接口/类的实例必须已经注入到了内置依赖注入容器中),把并这些实例存储到可枚举实例中。
/// </remarks>
/// <returns>
/// 返回:
/// 可枚举实例。该实例存储着1个指定接口/类的1/n实例。
/// </returns>
/// </summary>
public virtual IEnumerable<T> ResolveAll<T>()
{
return (IEnumerable<T>)GetServiceProvider().GetServices(typeof(T));
}
/// <param name="type">1个指定的类/接口的类型实例。</param>
/// <summary>
/// 【未注入解析】
/// <remarks>
/// 摘要:
/// 如果1指定的类/接口未注入到内置依赖注入容器中,通过该方法以反射方式获取1指定的类/接口的1/n个实例。
/// </remarks>
/// <returns>
/// 返回:
/// 1指定的类/接口的1/n个实例。
/// </returns>
/// </summary>
public virtual object ResolveUnregistered(Type type)
{
Exception innerException = null;
//尝试通过指定类的拷贝构造方法,获取该指定类的实例。
foreach (var constructor in type.GetConstructors())
{
try
{
//依次获取拷贝构造方法中所需参数实例。
var parameters = constructor.GetParameters().Select(parameter =>
{
var service = Resolve(parameter.ParameterType);
//如果拷贝构造方法中所需参数实例的实例值为:null(即参数实例,无实例值),则直接抛出异常信息。
if (service == null)
throw new NopException("该拷贝构造方法中有参数的实例值为:null");
return service;
});
//1指定的类/接口的类型实例及其所需的参数实例,以反射方式获取1指定的类/接口的1/n个实例。
return Activator.CreateInstance(type, parameters.ToArray());
}
catch (Exception ex)
{
innerException = ex;
}
}
throw new NopException("未能找指定类/接口的拷贝构造方法。", innerException);
}
#endregion
}
}
4 Core.Infrastructure.EngineContext
using System.Runtime.CompilerServices;
namespace Core.Infrastructure
{
/// <summary>
/// 【引擎上下文--类】
/// <remarks>
/// 摘要:
/// 实例化1个不受多线程操作影响的引擎接口单例实例(“Engine”(引擎)实例),并把该实例存储到单例类的字典属性成员实例中(副本/拷贝)。
/// </remarks>
/// </summary>
public class EngineContext
{
#region 属性
/// <summary>
/// 【当前】
/// <remarks>
/// 摘要:
/// 把1个不受多线程操作影响的引擎接口单例实例(“Engine”(引擎)实例)存储到单例类的字典属性成员实例中(副本/拷贝)。
/// </remarks>
/// </summary>
public static IEngine Current
{
get
{
if (Singleton<IEngine>.Instance == null)
{
Create();
}
return Singleton<IEngine>.Instance;
}
}
#endregion
#region 方法
/// <summary>
/// 【创建】
/// <remarks>
/// 摘要:
/// 实例化1个不受多线程操作影响的引擎接口单例实例(“Engine”(引擎)实例),即在程序运行过程中“Engine”实例是唯一的,该实例,在运行过程序中只能被替换(同内存的1个引用地址),而不能再次被实例化1次(两个不内存的引用地址)。
/// MethodImpl:
/// 通过“[MethodImpl(MethodImplOptions.Synchronized)]”标注,来确保在多线程操作不会对该单例实例产生影响,
/// 如果不使用“[MethodImpl(MethodImplOptions.Synchronized)]”标注,在多线程状态下也可能创建多个不同的“IEngine”单例实例,
/// 这样违反了“IEngine”实例被定义为单例实例且不受(在同一时间内)多线程操作影响的初衷。
/// [MethodImpl(MethodImplOptions.Synchronized)]、lock(this)与lock(typeof(...)):
/// 对于稍微有点经验的.NET开发人员来说,倘若被问及如何保持线程同步,
/// 我想很多人都能说好好几种。在众多的线程同步的可选方式中,加锁无疑是最为常用的。
/// 如果仅仅是基于方法级别的线程同步,
/// 使用System.Runtime.CompilerServices.MethodImplAttribute无疑是最为简洁的一种方式。
/// MethodImplAttribute可以用于instance method,
/// 也可以用于static method。当在某个方法上标注了MethodImplAttribute,
/// 并指定MethodImplOptions.Synchronized参数,
/// 可以确保在不同线程中运行的该方式以同步的方式运行。
/// 我们几天来讨论MethodImplAttribute(MethodImplOptions.Synchronized)和lock的关系。
/// </remarks>
/// <returns>
/// 返回:
/// 1个不受多线程操作影响的引擎接口单例实例(“Engine”(引擎)实例),即在程序运行过程中“Engine”实例是唯一的,该实例,在运行过程序中只能被替换(同内存的1个引用地址),而不能再次被实例化1次(两个不内存的引用地址)。
/// </returns>
/// </summary>
[MethodImpl(MethodImplOptions.Synchronized)]
public static IEngine Create()
{
//实例化1个不受多线程操作影响的引擎接口单例实例(“Engine”(引擎)实例),并把引擎接口单例实例(“Engine”(引擎)实例)存储到单例类的字典属性成员实例中(副本/拷贝),最后返回引擎接口单例实例(“Engine”(引擎)实例)。
return Singleton<IEngine>.Instance ?? (Singleton<IEngine>.Instance = new Engine());
}
/// <summary>
/// 【替换】
/// <param name="engine">一个新构建的不受多线程操作影响的引擎接口单例实例。</param>
/// <remarks>
/// 摘要:
/// 重新对存储在单例类的属性成员实例中的引擎接口单例实例(“Engine”(引擎)实例)进行覆盖,注意:这里的覆盖是针对单例类的字典属性成员实例中的引擎接口单例实例(副本/拷贝),而非擎接口单例实例(“Engine”(引擎)实例)本身,不是通过new关键字再实例化1个新的“Engine”(引擎)实例。
/// 说明:
/// 将一个新构建的“Engine”单例实例覆盖原有的程序中原有正在使用“Engine”单例实例,
/// 因为在同一次程序的执行过程中原有“Engine”单例实例的生命周期存在于在整个执行过程中,
/// 所有的操作使用的都是同一个实例,且该实例不能覆盖;而该方法却显式定义了对原有“Engine”单例实例的覆盖操作。
/// (这样显而易见同样违反了“Engine”单例实例被定义为单例实例且不受(在同一时间内)多线程操作影响的初衷,实质上该方法的功能是对该类功能下定义的否定),
/// 只有当开发者确认在极其极端情况下确且的需要使用一个新的“Engine”单例实例替换(覆盖)原有的“NEngine”单例实例的时候,才能够调用该方法。
/// </remarks>
/// </summary>
public static void Replace(IEngine engine)
{
Singleton<IEngine>.Instance = engine;
}
#endregion
}
}
5 Framework.Infrastructure.Extensions.ApplicationBuilderExtensions
using Core.Infrastructure;
using Microsoft.AspNetCore.Builder;
namespace Framework.Infrastructure.Extensions
{
/// <summary>
/// 【应用程序生成扩展--类】
/// <remarks>
/// 摘要:
/// 通过当前类中的成员方法把参与程序与页面的“应/答”操作的中间件,集成到.Net(Core)内置置管实例中。
/// </remarks>
/// </summary>
public static class ApplicationBuilderExtensions
{
/// <param name="application">.NetCore框架内置管道接口实例。</param>
/// <summary>
/// 【配置】
/// <remarks>
/// 摘要:
/// 通过.NetCore框架的内置管道接口实例,实例化“IServiceProvider”接口实例,同时把继承于“IStartup”所有具体现类中的中间件实例集成到.NetCore框架内置管道中。
/// </remarks>
public static void ConfigureRequestPipeline(this IApplicationBuilder application)
{
EngineContext.Current.ConfigureRequestPipeline(application);
}
}
}
6 Framework.Infrastructure.Extensions.ServiceCollectionExtensions. AddHttpContextAccessor
/// <param name="services">.Net(Core)框架内置依赖注入容器实例。</param>
/// <summary>
/// 【添加HTTP上下文访问器】
/// <remarks>
/// 摘要:
/// 1、为从内置依赖注入容器中获取生命周期被注册为“AddScoped”的具体实现类的实例提供支撑。
/// 2、在“应/答”操作中从指定的页面获取数据,为当前页面或新页面的渲染显示提供数据支撑,
/// </remarks>
/// </summary>
public static void AddHttpContextAccessor(this IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
7 Framework.Infrastructure.Extensions.ServiceCollectionExtensions. ConfigureApplicationServices
/// <param name="services">.Net(Core)框架内置依赖注入容器实例。</param>
/// <param name="builder">Web应用构建器的1个指定实例(Web应用构建器主要对基于.Net(Core)框架中的配置文件(*.json)进行读写操作,>=Net6)</param>
/// <summary>
/// 【配置应用程序服务】
/// <remarks>
/// 摘要:
/// 1、为从内置依赖注入容器中获取生命周期被注册为“AddScoped”的具体实现类的实例提供支撑。
/// 2、在“应/答”操作中从指定的页面获取数据,为当前页面或新页面的渲染显示提供数据支撑,
/// </remarks>
/// </summary>
public static void ConfigureApplicationServices(this IServiceCollection services, WebApplicationBuilder builder)
{
//把HTTP上下文访问器实例,注入到内置容器实例中。
services.AddHttpContextAccessor();
//实例化“Engine”(引擎)单例实例。
var engine = EngineContext.Create();
//把.NetCore框架内置依赖注入容器接口实例所存储的当前程序中的具体现类和中间件的实例通过“Engine”(引擎)单例实例存储到单例类的字典属性成员实例中。
engine.ConfigureServices(services, builder.Configuration);
}
8 Web.Controllers.InstallRefactoringController
using Core.Infrastructure;
using Data.Configuration;
using Data;
using Microsoft.AspNetCore.Mvc;
namespace Web.Controllers
{
public class InstallRefactoringController : Controller
{
#region 拷贝构造方法与变量
private readonly INopFileProvider _fileProvider;
public InstallRefactoringController(INopFileProvider fileProvider)
{
_fileProvider = fileProvider;
}
#endregion
public IActionResult Index(string message)
{
ViewBag.Message = message;
return View();
}
public async Task<IActionResult> DatabaseCreate()
{
DataSettingsManager.SaveSettings(new DataConfig
{
DataProvider = DataProviderType.SqlServer,
ConnectionString = "Data Source=.;Initial Catalog=ShopRazor;Integrated Security=False;Persist Security Info=False;User ID=zz;Password=zz;MultipleActiveResultSets=true;Trust Server Certificate=True"
}, _fileProvider);
//动态实例化EntityFrameworkCore中间件,而非通过当前类的拷贝构造方法实例化EntityFrameworkCore中间件,从而解决在生成数据库如果数据库连接字符串的同时,
//重启当前程序后才能执行创建数据库操作,否则会产生异常。
//1、因为当前类的拷贝构造方法实例化EntityFrameworkCore中间件是在程序启动前就被实例化的,如果无数据库如果数据库连接字符串,
//那么就会因EntityFrameworkCore中间件实例中不包含数据库如果数据库连接字符串,从而造成上述异常。
//2、动态实例化EntityFrameworkCore中间件,是在数据库连接字符串被实例化后,才被实例化的,
//所以就会因EntityFrameworkCore中间件实例中包含数据库如果数据库连接字符串,从而解决上述异常。
EFCoreContext _efCoreContext = EngineContext.Current.Resolve<EFCoreContext>();
//bool _isCreatedDatabase = await _efCoreContext.CreateDatabaseAndTableAsync();
bool _isCreatedDatabase = await _efCoreContext.Database.EnsureCreatedAsync();
return RedirectToAction("index", new { message = "持久化生成或覆盖数据库连接字符串,并以Code-First方式创建数据库。" });
}
}
}
9 重构Program.cs文件
using Core.Configuration;
using Core.Infrastructure;
using Data;
using Data.Configuration;
using Microsoft.EntityFrameworkCore;
using Framework.Infrastructure.Extensions;
using Core.Caching;
using LinqToDB.DataProvider.SqlServer;
var builder = WebApplication.CreateBuilder(args);
//如果启动项中不存在“appsettings.json”文件,则通过.Net(Core)的内置方法自动新建“appsettings.json”文件。
builder.Configuration.AddJsonFile("appsettings.json", true, true);
//把当前程序中所有继承了“IConfig”接口的具体实现类的实例,依赖注入到.Net(Core)内置依赖注入容器实例中,如果需要并把这些数据持久化存储到"appsettings.json"文件。
builder.Services.ConfigureApplicationSettings(builder);
builder.Services.AddScoped<INopFileProvider, NopFileProvider>();
builder.Services.AddSingleton<ILocker, MemoryCacheManager>();
builder.Services.AddSingleton<IStaticCacheManager, MemoryCacheManager>();
builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
//注意:在动态对EntityFrameworkCore中间件进行实例化时,必须使用“AddDbContextPool” 内置方法替换“AddDbContext”内置方法,
//否则就会出现异常:“System.InvalidOperationException: The service collection cannot be modified because it is read-only”。
builder.Services.AddDbContextPool<EFCoreContext>(options => {
//从单例实例的字典成员实例中获取当前程序所有配置相关数据。
AppSettings _appSettings = Singleton<AppSettings>.Instance;
//从应用配置类实例中获取数据库连接相关数据。
DataConfig _dataConfig = _appSettings.Get<DataConfig>();
//说明:如果想要“EntityFrameworkCore”中间件支持多数据库软件,则把选择条件中的所有中间件都注入到依赖注入到.Net(Core)框架内置容器即可,
//选择条件来限定当前程序只支持所设定的1个数据库软件,当然“DataConfig”类与“appsettings.json”文件也必须为支持多数据库软件进行重构。
if (_dataConfig.DataProvider.ToString().Equals("sqlserver", StringComparison.InvariantCultureIgnoreCase))
{
//通过“DbContextOptionsBuilder”实例中的参数实例,为“Microsoft.EntityFrameworkCore.SqlServer”中间件的实例化提供参数实例,
//最终把“Microsoft.EntityFrameworkCore.SqlServer”中间件实例,依赖注入到.Net(Core)框架内置容器中。
//IIS发布部署连接字符串必须使用“SQL Server身份认证”数据库连接方式,才能实现发布部署程序与数据库的CURD的操作。
options.UseSqlServer(_dataConfig.ConnectionString);
}
});
//通过AddRazorRuntimeCompilation依赖注入中间件实现页面修改热加载(Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation)。
builder.Services
.AddControllersWithViews()
.AddRazorRuntimeCompilation();
//把具体现类和中间件注入到内置依赖注入容器后,并把.NetCore框架内置依赖注入容器接口实例所存储的当前程序中的具体现类和中间件的实例通过“Engine”(引擎)单例实例存储到单例类的字典属性成员实例中。
//注意:从依赖注入到.Net(Core)框架内置容器中,获取“IServiceProvider”接口实例,必须定义在最后,
//否则“GetServices”/“GetRequiredService”方法将有可能不能获取取1个指定类的实例,因为该类的实例还没有依赖注入到.Net(Core)框架内置容器中。
builder.Services.ConfigureApplicationServices(builder);
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
/*注意:
在.net7最好不要直接使用app.UseEndpoints或app.UseMvc来集成路由匹配模式,否则会出现:“ASP0014”警告信息,
为了避免该警告信息直接使用最小API:app.MapControllerRoute来集成路由匹配模式。
*/
app.MapControllerRoute(
name: "areaRoute",
pattern: $"{{area:exists}}/{{controller=Install}}/{{action=Index}}/{{id?}}");
//动态实例化EntityFrameworkCore中间件,必须把InstallRefactoring/Index设定为默认启动页面,
//否则依然会因为当前类的拷贝构造方法实例化EntityFrameworkCore中间件是在程序启动前就被实例化的,如果无数据库如果数据库连接字符串,
//那么就会因EntityFrameworkCore中间件实例中不包含数据库如果数据库连接字符串,从而造成上述异常。
app.MapControllerRoute(
name: "default",
pattern: "{controller=InstallRefactoring}/{action=Index}/{id?}");
// 通过.NetCore框架的内置管道接口实例,实例化“IServiceProvider”接口实例,同时把继承于“IStartup”所有具体现类中的中间件实例集成到.NetCore框架内置管道中。
app.ConfigureRequestPipeline();
app.Run();
对以上功能更为具体实现和注释见230509_007ShopRazor(无连接字符串动态实例化EntityFrameworkCore中间件异常解决方案)。