当前位置: 首页 > news >正文

郑州做网站推广地收费下载资源 wordpress插件

郑州做网站推广地,收费下载资源 wordpress插件,北京seo实训班学校,wordpress上传的文件在哪个文件夹一、简要说明ABP vNext 框架在使用依赖注入服务的时候#xff0c;是直接使用的微软提供的 Microsoft.Extensions.DependencyInjection 包。这里与原来的 ABP 框架就不一样了#xff0c;原来的 ABP 框架还需要抽象出来一个 IIocManager 用来管理整个 IoC 容器#xff0c;现在… 一、简要说明ABP vNext 框架在使用依赖注入服务的时候是直接使用的微软提供的 Microsoft.Extensions.DependencyInjection 包。这里与原来的 ABP 框架就不一样了原来的 ABP 框架还需要抽象出来一个 IIocManager 用来管理整个 IoC 容器现在则直接操作 IServiceCollection 与 IServiceProvider 进行组件的注册/解析。这里需要注意的是虽然现在的依赖注入服务是使用微软官方那一套库进行操作但是 ABP vNext 还是为我们提供了组件自动注册、拦截器这些基础功能。二、源码分析2.1 组件自动注册ABP vNext 仍然在其 Core 库为我们提供了三种接口即 ISingletonDependency 和 ITransientDependency 、IScopedDependency 接口方便我们的类型/组件自动注册这三种接口分别对应了对象的 单例、瞬时、范围 生命周期。只要任何类型/接口实现了以上任意接口ABP vNext 就会在系统启动时候将这些对象注册到 IoC 容器当中。那么究竟是在什么时候呢回顾上一章的模块系统的文章在模块系统调用模块的 ConfigureService() 的时候就会有一个 services.AddAssembly(module.Type.Assembly) 他会将模块的所属的程序集传入。public class ModuleLoader : IModuleLoader{protected virtual void ConfigureServices(ListIAbpModuleDescriptor modules, IServiceCollection services){foreach (var module in modules) {if (module.Instance is AbpModule abpModule) {if (!abpModule.SkipAutoServiceRegistration) { services.AddAssembly(module.Type.Assembly); } } module.Instance.ConfigureServices(context); } }}看来核心就在于这个 AddAssembly() 扩展方法了跳转到方法的内部发现真正干事的是 IConventionalRegistrar 对象暂且称之为规约注册器而且我们可以拥有多个规约注册器你可以自己实现自动注册规则。public static IServiceCollection AddAssembly(this IServiceCollection services, Assembly assembly){foreach (var registrar in services.GetConventionalRegistrars()) { registrar.AddAssembly(services, assembly); }return services;}该接口定义了三个方法支持传入程序集、类型数组、具体类型他们的默认实现都在抽象类 ConventionalRegistrarBase 当中。public interface IConventionalRegistrar{void AddAssembly(IServiceCollection services, Assembly assembly);void AddTypes(IServiceCollection services, params Type[] types);void AddType(IServiceCollection services, Type type);}抽象类当中的实现也非常简单他们最终都是调用的 AddType() 方法来将类型注册到 IServiceCollection 当中的。public abstract class ConventionalRegistrarBase : IConventionalRegistrar{public virtual void AddAssembly(IServiceCollection services, Assembly assembly){var types AssemblyHelper .GetAllTypes(assembly) .Where( type type ! null type.IsClass !type.IsAbstract !type.IsGenericType ).ToArray(); AddTypes(services, types); }public virtual void AddTypes(IServiceCollection services, params Type[] types){foreach (var type in types) { AddType(services, type); } }public abstract void AddType(IServiceCollection services, Type type);}所以我们的重点就在于 AddType() 方法ABP vNext 框架默认的规约注册器叫做 DefaultConventionalRegistrar跳转到其定义可以发现在其内部除了对三种生命周期接口处理之外如果类型使用了 DependencyAttribute 特性也会根据该特性的参数配置进行不同的注册逻辑。public override void AddType(IServiceCollection services, Type type){if (IsConventionalRegistrationDisabled(type)) {return; }var dependencyAttribute GetDependencyAttributeOrNull(type);var lifeTime GetLifeTimeOrNull(type, dependencyAttribute);if (lifeTime null) {return; }foreach (var serviceType in AutoRegistrationHelper.GetExposedServices(services, type)) {var serviceDescriptor ServiceDescriptor.Describe(serviceType, type, lifeTime.Value);if (dependencyAttribute?.ReplaceServices true) { services.Replace(serviceDescriptor); }else if (dependencyAttribute?.TryRegister true) { services.TryAdd(serviceDescriptor); }else { services.Add(serviceDescriptor); } }}这里就是在 GetLifeTimeOrNull() 内部的 GetServiceLifetimeFromClassHierarcy() 方法确定了每个接口对应的生命周期。protected virtual ServiceLifetime? GetServiceLifetimeFromClassHierarcy(Type type){if (typeof(ITransientDependency).GetTypeInfo().IsAssignableFrom(type)) {return ServiceLifetime.Transient; }if (typeof(ISingletonDependency).GetTypeInfo().IsAssignableFrom(type)) {return ServiceLifetime.Singleton; }if (typeof(IScopedDependency).GetTypeInfo().IsAssignableFrom(type)) {return ServiceLifetime.Scoped; }return null;}如果读者有用过 AutoFac 或者 Castle Windsor 这些依赖注入框架的话就知道我们要注册一个类型需要知道该类型的定义和实现。这里的 AutoRegistrationHelper 工具类就会为我们确定注册类型的类型定义与其默认实现。例如我有两个接口 IDemoTest、IDemoTestTwo和他们的默认实现 DemoTest 我可以有以下几种方法来确定我的注册类型。[ExposeServices(typeof(IDemoTest),typeof(IDemoTestTwo))]public class DemoTest : IDemoTest,ITransientDependency{}public class DemoTest : IDemoTest,ITransientDependency{}public class DemoTest : ITransientDependency{}2.2 方法拦截器2.2.1 ABP vNext 新的抽象层在 ABP vNext 框架当中将方法拦截器抽象了一层 IAbpInterceptor但实际实现还是使用的 Castle.Core 所提供的动态代理功能其定义在 Volo.Abp.Dependency.DynamicProxy 文件夹当中如下图。ABP vNext 将拦截器和方法调用模型都进行了定义其中 AbpInterceptor 则是 IAbpInterceptor 的默认抽象实现。在ProxyHelper 工具类当中提供了从代理对象获取真实类型的方法。(PS: 通过 Castle.Core 代理后的对象与原有类型定义是不一致的。)public interface IAbpInterceptor{void Intercept(IAbpMethodInvocation invocation);Task InterceptAsync(IAbpMethodInvocation invocation);}public abstract class AbpInterceptor : IAbpInterceptor{public abstract void Intercept(IAbpMethodInvocation invocation);public virtual Task InterceptAsync(IAbpMethodInvocation invocation){ Intercept(invocation);return Task.CompletedTask; }}至于 IAbpMethodInvocation 接口则是封装了一个被拦截方法调用时的各种参数例如被拦截方法的在调用时所传递的参数返回值类型方法定义等。而 ABP vNext 也为它建立了一个 CastleAbpMethodInvocationAdapter 适配器实现了上述接口。public interface IAbpMethodInvocation{object[] Arguments { get; } IReadOnlyDictionarystring, object ArgumentsDictionary { get; } Type[] GenericArguments { get; }object TargetObject { get; } MethodInfo Method { get; }object ReturnValue { get; set; }void Proceed();Task ProceedAsync();}2.2.2 Castle.Core 动态代理的集成ABP vNext 在实际使用的时候还是通过 Castle.Core 提供的动态代理功能来实现拦截器相关的代码存放在 Volo.Abp.Castle.Core 库和 Volo.Abp.Autofac 库当中。首先我们来看 Castle.Core 库对接口 IAbpMethodInvocation 和 IAbpInterceptor 的实现在 CastleAbpInterceptorAdapter 中通过适配器来定义了一个标准的 Castle 拦截器这个拦截器可以传入 ABP vNext 定义的 IAbpInterceptor 作为其泛型参数。public class CastleAbpInterceptorAdapterTInterceptor : IInterceptorwhere TInterceptor : IAbpInterceptor{}Castle 的拦截器也会有一个 Intercept() 方法该方法将在被拦截方法执行的时候触发。在触发之后会根据当前方法的定义进行不同的操作这里异步方法和同步方法处理逻辑是不一样的。public void Intercept(IInvocation invocation){var proceedInfo invocation.CaptureProceedInfo();var method invocation.MethodInvocationTarget ?? invocation.Method;if (method.IsAsync()) { InterceptAsyncMethod(invocation, proceedInfo); }else { InterceptSyncMethod(invocation, proceedInfo); }}这里我们以异步方法为例其内部又会根据方法的返回值是否是 Task 进行不同的操作因为如果是泛型的 Task说明该异步方法是有返回值的所以处理逻辑也不一样。private void InterceptAsyncMethod(IInvocation invocation, IInvocationProceedInfo proceedInfo){if (invocation.Method.ReturnType typeof(Task)) { invocation.ReturnValue MethodExecuteWithoutReturnValueAsync .Invoke(this, new object[] { invocation, proceedInfo }); }else { invocation.ReturnValue MethodExecuteWithReturnValueAsync .MakeGenericMethod(invocation.Method.ReturnType.GenericTypeArguments[0]) .Invoke(this, new object[] {invocation, proceedInfo}); }}进一步解析在返回类型为 Task 时它所调用的方法。private async Task ExecuteWithoutReturnValueAsync(IInvocation invocation, IInvocationProceedInfo proceedInfo){await Task.Yield();await _abpInterceptor.InterceptAsync(new CastleAbpMethodInvocationAdapter(invocation, proceedInfo) );}从上述代码可以得知ABP vNext 的拦截器动作现在被包裹在一个 Castle 拦截器内部进行的。那么我们的 Castle.Core 拦截器在什么时候与类型进行绑定的呢每个拦截器又是如何与特性的类型进行注册的呢这里我以审计日志拦截器为例看一下它在系统当中是如何注册并被使用的。审计日志相关的代码存放在 Volo.Abp.Auditing 库中我们找到 AuditingInterceptor 类型查看其定义可以看到它也是继承自 AbpInterceptor 抽象基类。public class AuditingInterceptor : AbpInterceptor, ITransientDependency{}接着我们根据名字找到了拦截器的注册工具类 AuditingInterceptorRegistrar在类型的定义当中 ShouldIntercept() 与 ShouldAuditTypeByDefault() 根据传入的 Type 类型根据特定的逻辑决定是否为该类型关联审计日志拦截器。private static bool ShouldIntercept(Type type){if (ShouldAuditTypeByDefault(type)) {return true; }if (type.GetMethods().Any(m m.IsDefined(typeof(AuditedAttribute), true))) {return true; }return false;}public static bool ShouldAuditTypeByDefault(Type type){if (type.IsDefined(typeof(AuditedAttribute), true)) {return true; }if (type.IsDefined(typeof(DisableAuditingAttribute), true)) {return false; }if (typeof(IAuditingEnabled).IsAssignableFrom(type)) {return true; }return false;}我们这里需要关注的是 RegisterIfNeeded() 方法它在审计日志模块的预加载方法就被添加到了一个 ServiceRegistrationActionList 集合当中这个集合会在后面 AutoFac 进行类型注册的时候被使用。public static void RegisterIfNeeded(IOnServiceRegistredContext context){if (ShouldIntercept(context.ImplementationType)) { context.Interceptors.TryAddAuditingInterceptor(); }}public override void PreConfigureServices(ServiceConfigurationContext context){ context.Services.OnRegistred(AuditingInterceptorRegistrar.RegisterIfNeeded);}继续查看 OnRegistred() 的代码得到如下的定义可以看到最后的 Action 会被添加到一个 ServiceRegistrationActionList 访问器中。public static void OnRegistred(this IServiceCollection services, ActionIOnServiceRegistredContext registrationAction){ GetOrCreateRegistrationActionList(services).Add(registrationAction);}public static ServiceRegistrationActionList GetRegistrationActionList(this IServiceCollection services){return GetOrCreateRegistrationActionList(services);}private static ServiceRegistrationActionList GetOrCreateRegistrationActionList(IServiceCollection services){var actionList services.GetSingletonInstanceOrNullIObjectAccessorServiceRegistrationActionList()?.Value;if (actionList null) { actionList new ServiceRegistrationActionList(); services.AddObjectAccessor(actionList); }return actionList;}AutoFac 在执行注册操作的时候会调用 AutofacRegistration 静态类的 Register 方法该方法会遍历整个 IServiceCollection 集合。在将类型注册到 AutoFac 的 IoC 容器中的时候在它的内部会调用 AbpRegistrationBuilderExtensions 提供的扩展方法为具体的类型添加过滤器。private static void Register( ContainerBuilder builder, IServiceCollection services){var moduleContainer services.GetSingletonInstanceIModuleContainer();var registrationActionList services.GetRegistrationActionList();foreach (var service in services) {if (service.ImplementationType ! null) {var serviceTypeInfo service.ServiceType.GetTypeInfo();if (serviceTypeInfo.IsGenericTypeDefinition) { builder .RegisterGeneric(service.ImplementationType) .As(service.ServiceType) .ConfigureLifecycle(service.Lifetime) .ConfigureAbpConventions(moduleContainer, registrationActionList); } } }}下面是扩展方法所定义的相关代码注意阅读注释。public static IRegistrationBuilderTLimit, TActivatorData, TRegistrationStyle ConfigureAbpConventionsTLimit, TActivatorData, TRegistrationStyle(this IRegistrationBuilderTLimit, TActivatorData, TRegistrationStyle registrationBuilder, IModuleContainer moduleContainer, ServiceRegistrationActionList registrationActionList)where TActivatorData : ReflectionActivatorData{ registrationBuilder registrationBuilder.InvokeRegistrationActions(registrationActionList, serviceType, implementationType);}private static IRegistrationBuilderTLimit, TActivatorData, TRegistrationStyle InvokeRegistrationActionsTLimit, TActivatorData, TRegistrationStyle(this IRegistrationBuilderTLimit, TActivatorData, TRegistrationStyle registrationBuilder, ServiceRegistrationActionList registrationActionList, Type serviceType, Type implementationType)where TActivatorData : ReflectionActivatorData{var serviceRegistredArgs new OnServiceRegistredContext(serviceType, implementationType);foreach (var registrationAction in registrationActionList) { registrationAction.Invoke(serviceRegistredArgs); }if (serviceRegistredArgs.Interceptors.Any()) { registrationBuilder registrationBuilder.AddInterceptors( serviceType, serviceRegistredArgs.Interceptors ); }return registrationBuilder;}private static IRegistrationBuilderTLimit, TActivatorData, TRegistrationStyle AddInterceptorsTLimit, TActivatorData, TRegistrationStyle(this IRegistrationBuilderTLimit, TActivatorData, TRegistrationStyle registrationBuilder, Type serviceType, IEnumerableType interceptors)where TActivatorData : ReflectionActivatorData{foreach (var interceptor in interceptors) { registrationBuilder.InterceptedBy(typeof(CastleAbpInterceptorAdapter).MakeGenericType(interceptor) ); }return registrationBuilder;}2.3 对象访问器在第一章节的时候我们就遇到过 IObjectAccessorT 接口基本上是针对该接口所提供的 Value 属性进行操作下面就是该接口的定义和它的默认实现 ObjectAccessorT十分简单就一个泛型的 Value。public interface IObjectAccessorout T{ [CanBeNull] T Value { get; }}public class ObjectAccessorT : IObjectAccessorT{public T Value { get; set; }public ObjectAccessor(){ }public ObjectAccessor([CanBeNull] T obj){ Value obj; }}仅仅看上述的代码是看不出什么名堂的接着我们来到它的扩展方法定义 ServiceCollectionObjectAccessorExtensions 。可以看到其核心的代码在于 ObjectAccessorT AddObjectAccessorT(this IServiceCollection services, ObjectAccessorT accessor)这个重载方法。它首先判断某个特定泛型的对象访问器是否被注册如果被注册直接抛出异常没有则继续。最后呢通过一个小技巧将某个特定类型的对象访问器作为单例注册到 IoC 容器的头部方便快速检索。public static ObjectAccessorT AddObjectAccessorT(this IServiceCollection services, ObjectAccessorT accessor){if (services.Any(s s.ServiceType typeof(ObjectAccessorT))) {throw new Exception(An object accessor is registered before for type: typeof(T).AssemblyQualifiedName); } services.Insert(0, ServiceDescriptor.Singleton(typeof(ObjectAccessorT), accessor)); services.Insert(0, ServiceDescriptor.Singleton(typeof(IObjectAccessorT), accessor));return accessor;}使用的时候从第一章就有见到这里的对象访问器可以传入一个类型。这个时候其 Value 就是空的但并不影响该类型的解析只需要在真正使用之前将其 Value 值赋值为实例对象即可。只是目前来看该类型的作用并不是十分明显更多的时候是一个占位类型而已你可以在任意时间替换某个类型的对象访问器内部的 Value 值。2.4 服务的范围工厂我们知道在依赖注入框架当中有一种特别的生命周期叫做 Scoped 周期这个周期在我之前的相关文章有讲过它是一个比较特别的生命周期。简单来说Scoped 对象的生命周期只有在某个范围内是单例存在的例如以下伪代码用户会请求 ScopedTest() 接口public class HomeController(){public Task ScopedTest(){using(var scope ScopedFactory.CreateScopeTestApp()) { scope.ChildContainer.ResolveTestApp.Name 111; scope.ChildContainer.ResolveTestController(); } }}public class TestController(){public TestController(TestApp app){ Console.WritleLine(app.Name); }}最后在 TestController 中控制台会输出 111 作为结果在 HomeController 中 ScopedTest() 语句块结束的时候obj 对象会被释放在后续的请求当中TestApp 都是作为一个 Scoped 对象生存的。所以流程可以分为以下几步通过 ScopeFactory 创建一个 Scope 范围。通过 Scope 范围内的子容器解析对象。子容器在解析时如果解析出来的类型是 Scope 生命周期则在整个 Scope 存活期间它都是单例的。Scope 范围释放会调用销毁内部的子容器并销毁掉所有解析出来的对象。在 Volo.Abp.Autofac 库当中定义了使用 AutoFac 封装的范围工厂与服务范围类型的定义他们将会作为默认的 IServiceScopeFactory 实现。internal class AutofacServiceScopeFactory : IServiceScopeFactory{private readonly ILifetimeScope _lifetimeScope;public AutofacServiceScopeFactory(ILifetimeScope lifetimeScope){this._lifetimeScope lifetimeScope; }public IServiceScope CreateScope(){return new AutofacServiceScope(this._lifetimeScope.BeginLifetimeScope()); }}这里可以看到在构建这个工厂的时候会注入一个 ILifetimScope这个东西就是 AutoFac 提供的 子容器。在 CreateScope() 方法内部我们通过构造一个 Scope 作为具体的范围解析对象并将子容器传入到它的内部。internal class AutofacServiceScope : IServiceScope{private readonly ILifetimeScope _lifetimeScope;public AutofacServiceScope(ILifetimeScope lifetimeScope){this._lifetimeScope lifetimeScope;this.ServiceProvider this._lifetimeScope.ResolveIServiceProvider(); }public IServiceProvider ServiceProvider { get; }public void Dispose(){this._lifetimeScope.Dispose(); }}那么是在什么时候我们的范围工厂会被调用来构造一个 IServiceScope 对象呢就是在 ASP.NET Core 每次请求的时候它在获得其内部的 RequestServices 时就会通过 IServiceProvidersFeature 来创建一个 Scope 范围。public IServiceProvider RequestServices{get {if (!_requestServicesSet) { _context.Response.RegisterForDispose(this); _scope _scopeFactory.CreateScope(); _requestServices _scope.ServiceProvider; _requestServicesSet true; }return _requestServices; }set { _requestServices value; _requestServicesSet true; }}所以我们在每次请求的时候针对于 Scope 声明周期的对象默认的话都是在整个请求处理期间都是单例的除非显式使用 using 语句块声明作用域。而在 ABP vNext 中给我们提供了两个 Scoped Factory分别是 HttpContextServiceScopeFactory 和 DefaultServiceScopeFactory 它们都继承自 IHybridServiceScopeFactory 接口。这个 IHybridServiceScopeFactory 接口只是一个空的接口并继承自 Microsoft Dependency Inject 提供的 IServiceScopeFactory 工厂接口。但在实际注入的时候并不会替换掉默认的 IServiceScopeFactory 实现。因为在 IHybridServiceScopeFactory 的默认两个实现的定义上他们都显式得通过 ExposeServices 特性说明了自己是哪些类型的默认实现且一般使用的时候都是通过注入 IHybridServiceScopeFactory并结合 using 语句块来操作。例如在 Volo.Abp.Data 库的 DataSeeder 类型中有如下用法。public async Task SeedAsync(DataSeedContext context){using (var scope ServiceScopeFactory.CreateScope()) {foreach (var contributorType in Options.Contributors) {var contributor (IDataSeedContributor) scope .ServiceProvider .GetRequiredService(contributorType);await contributor.SeedAsync(context); } }}只是这两个实现有什么不同呢通过两个类型的名字就可以看出来一个是给 ASP.NET Core MVC 程序使用的另一个则是默认的范围工厂下面我们从代码层面上来比较一下两者之间的差别。[ExposeServices( typeof(IHybridServiceScopeFactory), typeof(DefaultServiceScopeFactory) )]public class DefaultServiceScopeFactory : IHybridServiceScopeFactory, ITransientDependency{protected IServiceScopeFactory Factory { get; }public DefaultServiceScopeFactory(IServiceScopeFactory factory){ Factory factory; }public IServiceScope CreateScope(){return Factory.CreateScope(); }}HttpContextServiceScopeFactory 是放在 AspNetCore 模块下的从他的 Dependency 特性可以看出来他会替换掉默认的 DefaultServiceScopeFactory 实现。[ExposeServices( typeof(IHybridServiceScopeFactory), typeof(HttpContextServiceScopeFactory) )][Dependency(ReplaceServices true)]public class HttpContextServiceScopeFactory : IHybridServiceScopeFactory, ITransientDependency{protected IHttpContextAccessor HttpContextAccessor { get; }protected IServiceScopeFactory ServiceScopeFactory { get; }public HttpContextServiceScopeFactory( IHttpContextAccessor httpContextAccessor, IServiceScopeFactory serviceScopeFactory){ HttpContextAccessor httpContextAccessor; ServiceScopeFactory serviceScopeFactory; }public virtual IServiceScope CreateScope(){var httpContext HttpContextAccessor.HttpContext;if (httpContext null) {return ServiceScopeFactory.CreateScope(); }return new NonDisposedHttpContextServiceScope(httpContext.RequestServices); }protected class NonDisposedHttpContextServiceScope : IServiceScope {public IServiceProvider ServiceProvider { get; }public NonDisposedHttpContextServiceScope(IServiceProvider serviceProvider){ ServiceProvider serviceProvider; }public void Dispose(){ } }}可以看到后者如果在 HttpContext 不为 null 的时候是使用的 HttpContext.RequestServices 作为这个 Scope 的解析器。RequestServices, on the other hand, is a scoped container created from the root on each request.翻译成中文的意思就是它是在每个请求的的时候创建的独立范围容器其实就是开头所说的子容器。2.5 类型注册完成的动作其实这个玩意儿应该放在 2.2 节之前讲只是在写完之后我才看到相关类型是放在依赖注入相关的文件夹当中这里还请各位读者理解一下。早期在 Castle Windsor 当中类型在注册完成的时候会有一个注册完成的事件用户可以挂载该事件来进行一些特殊的处理比如说为类型添加动态代理。在 ABP vNext 当中因为支持多种不同的依赖注入框架所以就没有类似的事件来做处理。ABP vNext 则封装了一个 ServiceRegistrationActionList 类型该类型用于存储在类型注册完成之后用户可以执行的操作可以看到它就是一个 Action 集合用于存放一系列回调方法。public class ServiceRegistrationActionList : ListActionIOnServiceRegistredContext{}由 2.2 节得知这个玩意儿是在每一个类型注册完成之后都会被遍历调用其中的 Action 动作。在调用的时候会将当前注册完成的类型封装成一个 IOnServiceRegistredContext 对象传递给具体的委托这样委托就能够知道当前调用的类型也就能够将拦截器放在其 Interceptors 属性当中了。public interface IOnServiceRegistredContext{ ITypeListIAbpInterceptor Interceptors { get; } Type ImplementationType { get; }}三、总结ABP vNext 框架针对于依赖注入这块的工作也进行了大量的精简就代码量来说比原有 ABP 框架减少了差不多一半左右而且整个逻辑也比原来更加简洁易懂。开发人员在使用的时候其实最多的是关注如何注入自己想要的类型。通过了解 ABP vNext 底层的代码 方便我们清楚拦截器和依赖注入框架的具体过程这样在后面扩展功能的时候才能够做到心中有数。原文地址https://www.cnblogs.com/myzony/p/10755010.html.NET社区新闻深度好文欢迎访问公众号文章汇总 http://www.csharpkit.com
http://www.zqtcl.cn/news/208238/

相关文章:

  • mvc5 网站开发之美专业企业建站价格
  • 水果电子商务网站建设规划书ipad做网站服务器
  • 网站模版自适应安卓软件开发培训
  • 网络网站建设10大指标开店装修话做那个网站找工人
  • dedecms网站的下载济南网站忧化
  • 深圳北站设计者亚洲国产中文域名查询
  • 有好的学网站建设的书吗龙岗网站建设服务
  • 建个注册页面网站做网站坚持多少年会有起色
  • 做网站是什么职位工商局网站查询入口
  • 做腰椎核磁证网站是 收 七如何做个盈利的网站
  • wordpress查看站点购物网站的后台做哪些东西
  • 文化馆为何需要建设自己的网站网站的建设教程
  • o2o网站策划京北网app下载
  • 公众号链接电影网站怎么做禁止wordpress保存修订版
  • 免费网站建设排行网站开发需要注册账户吗
  • 深圳营销网站建设免费网站添加站长统计
  • 建设银行网站怎么能转账代理ip注册网站都通不过
  • 一台服务器做两个网站吗明空网络做网站好不好
  • 正定县建设局网站东莞微信网站建设咨询
  • 网站开发免费视频教程网站备案帐号是什么情况
  • 知名门户网站小程序页面设计报价
  • 蒲城矿建设备制造厂网站喀什哪有做网站的
  • 网站内页产品做跳转三合一商城网站
  • 做网站找不到客户兰州 网站制作
  • 广州中小学智慧阅读门户网站中山网站建设方案推广
  • 长沙网站建设专家排行榜
  • 清河企业做网站wordpress淘宝客插件开发
  • 网站上传连接失败的原因微信app网站建设
  • 服装网站源码php建设厅网站合同备案在哪里
  • o2o网站建设多少钱公司设计网站定制