ASP.NET Core Controller與IOC結(jié)合問題整理
前言
看到標(biāo)題可能大家會(huì)有所疑問Controller和IOC能有啥羈絆,但是我還是拒絕當(dāng)一個(gè)標(biāo)題黨的。相信有很大一部分人已經(jīng)知道了這么一個(gè)結(jié)論,默認(rèn)情況下ASP.NET Core的Controller并不會(huì)托管到IOC容器中,注意關(guān)鍵字我說的是"默認(rèn)",首先咱們不先說為什么,如果還有不知道這個(gè)結(jié)論的同學(xué)們可以自己驗(yàn)證一下,驗(yàn)證方式也很簡(jiǎn)單,大概可以通過以下幾種方式。
驗(yàn)證Controller不在IOC中
首先,我們可以嘗試在ServiceProvider中獲取某個(gè)Controller實(shí)例,比如
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { var productController = app.ApplicationServices.GetService<ProductController>(); }
這是最直接的方式,可以在IOC容器中獲取注冊(cè)過的類型實(shí)例,很顯然結(jié)果會(huì)為null。另一種方式,也是利用它的另一個(gè)特征,那就是通過構(gòu)造注入的方式,如下所示我們?cè)贠rderController中注入ProductController,顯然這種方式是不合理的,但是為了求證一個(gè)結(jié)果,我們這里僅做演示,強(qiáng)烈不建議實(shí)際開發(fā)中這么寫,這是不規(guī)范也是不合理的寫法
public class OrderController : Controller { private readonly ProductController _productController; public OrderController(ProductController productController) { _productController = productController; } public IActionResult Index() { return View(); } }
結(jié)果顯然是會(huì)報(bào)一個(gè)錯(cuò)InvalidOperationException: Unable to resolve service for type 'ProductController' while attempting to activate 'OrderController'。原因就是因?yàn)镻roductController并不在IOC容器中,所以通過注入的方式會(huì)報(bào)錯(cuò)。還有一種方式,可能不太常用,這個(gè)是利用注入的一個(gè)特征,可能有些同學(xué)已經(jīng)了解過了,那就是通過自帶的DI,即使一個(gè)類中包含多個(gè)構(gòu)造函數(shù),它也會(huì)選擇最優(yōu)的一個(gè),也就是說自帶的DI允許類包含多個(gè)構(gòu)造函數(shù)。利用這個(gè)特征,我們可以在Controller中驗(yàn)證一下
public class OrderController : Controller { private readonly IOrderService _orderService; private readonly IPersonService _personService; public OrderController(IOrderService orderService) { _orderService = orderService; } public OrderController(IOrderService orderService, IPersonService personService) { _orderService = orderService; _personService = personService; } public IActionResult Index() { return View(); } }
我們?cè)贑ontroller中編寫了兩個(gè)構(gòu)造函數(shù),理論上來說這是符合DI特征的,運(yùn)行起來測(cè)試一下,依然會(huì)報(bào)錯(cuò)InvalidOperationException: Multiple constructors accepting all given argument types have been found in type 'OrderController'. There should only be one applicable constructor。以上種種都是為了證實(shí)一個(gè)結(jié)論,默認(rèn)情況下Controller并不會(huì)托管到IOC當(dāng)中。
DefaultControllerFactory源碼探究
上面雖然我們看到了一些現(xiàn)象,能說明Controller默認(rèn)情況下并不在IOC中托管,但是還沒有足夠的說服力,接下來我們就來查看源碼,這是最有說服力的。我們找到Controller工廠注冊(cè)的地方,在MvcCoreServiceCollectionExtensions擴(kuò)展類中[點(diǎn)擊查看源碼]的AddMvcCoreServices方法里
//給IControllerFactory注冊(cè)默認(rèn)的Controller工廠類DefaultControllerFactory //也是Controller創(chuàng)建的入口 services.TryAddSingleton<IControllerFactory, DefaultControllerFactory>(); //真正創(chuàng)建Controller的工作類DefaultControllerActivator services.TryAddTransient<IControllerActivator, DefaultControllerActivator>();
由此我們可以得出,默認(rèn)的Controller創(chuàng)建工廠類為DefaultControllerFactory,那么我們直接找到源碼位置[點(diǎn)擊查看源碼],
為了方便閱讀,精簡(jiǎn)一下源碼如下所示
internal class DefaultControllerFactory : IControllerFactory { //真正創(chuàng)建Controller的工作者 private readonly IControllerActivator _controllerActivator; private readonly IControllerPropertyActivator[] _propertyActivators; public DefaultControllerFactory( IControllerActivator controllerActivator, IEnumerable<IControllerPropertyActivator> propertyActivators) { _controllerActivator = controllerActivator; _propertyActivators = propertyActivators.ToArray(); } /// <summary> /// 創(chuàng)建Controller實(shí)例的方法 /// </summary> public object CreateController(ControllerContext context) { //創(chuàng)建Controller實(shí)例的具體方法(這是關(guān)鍵方法) var controller = _controllerActivator.Create(context); foreach (var propertyActivator in _propertyActivators) { propertyActivator.Activate(context, controller); } return controller; } /// <summary> /// 釋放Controller實(shí)例的方法 /// </summary> public void ReleaseController(ControllerContext context, object controller) { _controllerActivator.Release(context, controller); } }
用過上面的源碼可知,真正創(chuàng)建Controller的地方在_controllerActivator.Create方法中,通過上面的源碼可知為IControllerActivator默認(rèn)注冊(cè)的是DefaultControllerActivator類,直接找到源碼位置[點(diǎn)擊查看源碼],我們繼續(xù)簡(jiǎn)化一下源碼如下所示
internal class DefaultControllerActivator : IControllerActivator { private readonly ITypeActivatorCache _typeActivatorCache; public DefaultControllerActivator(ITypeActivatorCache typeActivatorCache) { _typeActivatorCache = typeActivatorCache; } /// <summary> /// Controller實(shí)例的創(chuàng)建方法 /// </summary> public object Create(ControllerContext controllerContext) { //獲取Controller類型信息 var controllerTypeInfo = controllerContext.ActionDescriptor.ControllerTypeInfo; //獲取ServiceProvider var serviceProvider = controllerContext.HttpContext.RequestServices; //創(chuàng)建controller實(shí)例 return _typeActivatorCache.CreateInstance<object>(serviceProvider, controllerTypeInfo.AsType()); } /// <summary> /// 釋放Controller實(shí)例 /// </summary> public void Release(ControllerContext context, object controller) { //如果controller實(shí)現(xiàn)了IDisposable接口,那么Release的時(shí)候會(huì)自動(dòng)調(diào)用Controller的Dispose方法 //如果我們?cè)贑ontroller中存在需要釋放或者關(guān)閉的操作,可以再Controller的Dispose方法中統(tǒng)一釋放 if (controller is IDisposable disposable) { disposable.Dispose(); } } }
通過上面的代碼我們依然要繼續(xù)深入到ITypeActivatorCache實(shí)現(xiàn)中去尋找答案,通過查看MvcCoreServiceCollectionExtensions類的AddMvcCoreServices方法源碼我們可以找到如下信息
services.TryAddSingleton<ITypeActivatorCache, TypeActivatorCache>();
有了這個(gè)信息,我們可以直接找到TypeActivatorCache類的源碼[點(diǎn)擊查看源碼]代碼并不多,大致如下所示
internal class TypeActivatorCache : ITypeActivatorCache { //創(chuàng)建ObjectFactory的委托 private readonly Func<Type, ObjectFactory> _createFactory = (type) => ActivatorUtilities.CreateFactory(type, Type.EmptyTypes); //Controller類型和對(duì)應(yīng)創(chuàng)建Controller實(shí)例的ObjectFactory實(shí)例的緩存 private readonly ConcurrentDictionary<Type, ObjectFactory> _typeActivatorCache = new ConcurrentDictionary<Type, ObjectFactory>(); /// <summary> /// 真正創(chuàng)建實(shí)例的地方 /// </summary> public TInstance CreateInstance<TInstance>( IServiceProvider serviceProvider, Type implementationType) { //真正創(chuàng)建的操作是createFactory //通過Controller類型在ConcurrentDictionary緩存中獲得ObjectFactory //而ObjectFactory實(shí)例由ActivatorUtilities.CreateFactory方法創(chuàng)建的 var createFactory = _typeActivatorCache.GetOrAdd(implementationType, _createFactory); //返回創(chuàng)建實(shí)例 return (TInstance)createFactory(serviceProvider, arguments: null); } }
通過上面類的代碼我們可以清晰的得出一個(gè)結(jié)論,默認(rèn)情況下Controller實(shí)例是由ObjectFactory創(chuàng)建出來的,而ObjectFactory實(shí)例是由ActivatorUtilities的CreateFactory創(chuàng)建出來,所以Controller實(shí)例每次都是由ObjectFactory創(chuàng)建而來,并非注冊(cè)到IOC容器中。并且我們還可以得到一個(gè)結(jié)論ObjectFactory應(yīng)該是一個(gè)委托,我們找到ObjectFactory定義的地方[點(diǎn)擊查看源碼]
delegate object ObjectFactory(IServiceProvider serviceProvider, object[] arguments);
這個(gè)確實(shí)如我們猜想的那般,這個(gè)委托會(huì)通過IServiceProvider實(shí)例去構(gòu)建類型的實(shí)例,通過上述源碼相關(guān)的描述我們會(huì)產(chǎn)生一個(gè)疑問,既然Controller實(shí)例并非由IOC容器托管,它由ObjectFactory創(chuàng)建而來,但是ObjectFactory實(shí)例又是由ActivatorUtilities構(gòu)建的,那么生產(chǎn)對(duì)象的核心也就在ActivatorUtilities類中,接下來我們就來探究一下ActivatorUtilities的神秘面紗。
ActivatorUtilities類的探究
書接上面,我們知道了ActivatorUtilities類是創(chuàng)建Controller實(shí)例最底層的地方,那么ActivatorUtilities到底和容器是啥關(guān)系,因?yàn)槲覀兛吹搅薃ctivatorUtilities創(chuàng)建實(shí)例需要依賴ServiceProvider,一切都要從找到ActivatorUtilities類的源碼開始。我們最初接觸這個(gè)類的地方在于它通過CreateFactory方法創(chuàng)建了ObjectFactory實(shí)例,那么我們就從這個(gè)地方開始,找到源碼位置[點(diǎn)擊查看源碼]實(shí)現(xiàn)如下
public static ObjectFactory CreateFactory(Type instanceType, Type[] argumentTypes) { //查找instanceType的構(gòu)造函數(shù) //找到構(gòu)造信息ConstructorInfo //得到給定類型與查找類型instanceType構(gòu)造函數(shù)的映射關(guān)系 FindApplicableConstructor(instanceType, argumentTypes, out ConstructorInfo constructor, out int?[] parameterMap); //構(gòu)建IServiceProvider類型參數(shù) var provider = Expression.Parameter(typeof(IServiceProvider), "provider"); //構(gòu)建給定類型參數(shù)數(shù)組參數(shù) var argumentArray = Expression.Parameter(typeof(object[]), "argumentArray"); //通過構(gòu)造信息、構(gòu)造參數(shù)對(duì)應(yīng)關(guān)系、容器和給定類型構(gòu)建表達(dá)式樹Body var factoryExpressionBody = BuildFactoryExpression(constructor, parameterMap, provider, argumentArray); //構(gòu)建lambda var factoryLamda = Expression.Lambda<Func<IServiceProvider, object[], object>>( factoryExpressionBody, provider, argumentArray); var result = factoryLamda.Compile(); //返回執(zhí)行結(jié)果 return result.Invoke; }
ActivatorUtilities類的CreateFactory方法代碼雖然比較簡(jiǎn)單,但是它涉及到調(diào)用了其他方法,由于嵌套的比較深代碼比較多,而且不是本文講述的重點(diǎn),我們就不再這里細(xì)說了,我們可以大概的描述一下它的工作流程。
- 首先在給定的類型里查找到合適的構(gòu)造函數(shù),這里我們可以理解為查找Controller的構(gòu)造函數(shù)。
- 然后得到構(gòu)造信息,并得到構(gòu)造函數(shù)的參數(shù)與給定類型參數(shù)的對(duì)應(yīng)關(guān)系
- 通過構(gòu)造信息和構(gòu)造參數(shù)的對(duì)應(yīng)關(guān)系,在IServiceProvider得到對(duì)應(yīng)類型的實(shí)例為構(gòu)造函數(shù)賦值
- 最后經(jīng)過上面的操作通過初始化指定的構(gòu)造函數(shù)來創(chuàng)建給定Controller類型的實(shí)例
綜上述的相關(guān)步驟,我們可以得到一個(gè)結(jié)論,Controller實(shí)例的初始化是通過遍歷Controller類型構(gòu)造函數(shù)里的參數(shù),然后根據(jù)構(gòu)造函數(shù)每個(gè)參數(shù)的類型在IServiceProvider查找已經(jīng)注冊(cè)到容器中相關(guān)的類型實(shí)例,最終初始化得到的Controller實(shí)例。這就是在IServiceProvider得到需要的依賴關(guān)系,然后創(chuàng)建自己的實(shí)例,它內(nèi)部是使用的表達(dá)式樹來完成的這一切,可以理解為更高效的反射方式。
關(guān)于ActivatorUtilities類還包含了其他比較實(shí)用的方法,比如CreateInstance方法
public static T CreateInstance<T>(IServiceProvider provider, params object[] parameters)
它可以通過構(gòu)造注入的方式創(chuàng)建指定類型T的實(shí)例,其中構(gòu)造函數(shù)里具體的參數(shù)實(shí)例是通過在IServiceProvider實(shí)例里獲取到的,比如我們我們有這么一個(gè)類
public class OrderController { private readonly IOrderService _orderService; private readonly IPersonService _personService; public OrderController(IOrderService orderService, IPersonService personService) { _orderService = orderService; _personService = personService; } }
其中它所依賴的IOrderService和IPersonService實(shí)例是注冊(cè)到IOC容器中的
IServiceCollection services = new ServiceCollection() .AddScoped<IPersonService, PersonService>() .AddScoped<IOrderService, OrderService>();
然后你想獲取到OrderController的實(shí)例,但是它只包含一個(gè)有參構(gòu)造函數(shù),但是構(gòu)造函數(shù)的參數(shù)都以注冊(cè)到IOC容器中。當(dāng)存在這種場(chǎng)景你便可以通過以下方式得到你想要的類型實(shí)例,如下所示
IServiceProvider serviceProvider = services.BuildServiceProvider(); OrderController orderController = ActivatorUtilities.CreateInstance<OrderController>(serviceProvider);
即使你的類型OrderController并沒有注冊(cè)到IOC容器中,但是它的依賴都在容器中,你也可以通過構(gòu)造注入的方式得到你想要的實(shí)例。總的來說ActivatorUtilities里的方法還是比較實(shí)用的,有興趣的同學(xué)可以自行嘗試一下,也可以通過查看ActivatorUtilities源碼的方式了解它的工作原理。
AddControllersAsServices方法
上面我們主要是講解了默認(rèn)情況下Controller并不是托管到IOC容器中的,它只是表現(xiàn)出來的讓你以為它是在IOC容器中,因?yàn)樗梢酝ㄟ^構(gòu)造函數(shù)注入相關(guān)實(shí)例,這主要是ActivatorUtilities類的功勞。說了這么多Controller實(shí)例到底可不可以注冊(cè)到IOC容器中,讓它成為真正受到IOC容器的托管者。要解決這個(gè),必須要滿足兩點(diǎn)條件
- 首先,需要將Controller注冊(cè)到IOC容器中,但是僅僅這樣還不夠,因?yàn)镃ontroller是由ControllerFactory創(chuàng)建而來
- 其次,我們要改造ControllerFactory類中創(chuàng)建Controller實(shí)例的地方讓它從容器中獲取Controller實(shí)例,這樣就解決了所有的問題
如果我們自己去實(shí)現(xiàn)將Controller托管到IOC容器中,就需要滿足以上兩個(gè)操作一個(gè)是要將Controller放入容器,然后讓創(chuàng)建Controller的地方從IOC容器中直接獲取Controller實(shí)例。慶幸的是,微軟幫我們封裝了一個(gè)相關(guān)的方法,它可以幫我們解決將Controller托管到IOC容器的問題,它的使用方法如下所示
services.AddMvc().AddControllersAsServices(); //或其他方式,這取決于你構(gòu)建的Web項(xiàng)目的用途可以是WebApi、Mvc、RazorPage等 //services.AddMvcCore().AddControllersAsServices();
相信大家都看到了,玄機(jī)就在AddControllersAsServices方法中,但是它存在于MvcCoreMvcBuilderExtensions類和MvcCoreMvcCoreBuilderExtensions類中,不過問題不大,因?yàn)樗鼈兊拇a是完全一樣的。只是因?yàn)槟憧梢酝ㄟ^多種方式構(gòu)建Web項(xiàng)目比如AddMvc或者AddMvcCore,廢話不多說直接上代碼[點(diǎn)擊查看源碼]
public static IMvcBuilder AddControllersAsServices(this IMvcBuilder builder) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } var feature = new ControllerFeature(); builder.PartManager.PopulateFeature(feature); //第一將Controller實(shí)例添加到IOC容器中 foreach (var controller in feature.Controllers.Select(c => c.AsType())) { //注冊(cè)的聲明周期是Transient builder.Services.TryAddTransient(controller, controller); } //第二替換掉原本DefaultControllerActivator的為ServiceBasedControllerActivator builder.Services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>()); return builder; }
第一點(diǎn)沒問題那就是將Controller實(shí)例添加到IOC容器中,第二點(diǎn)它替換掉了DefaultControllerActivator為為ServiceBasedControllerActivator。通過上面我們講述的源碼了解到DefaultControllerActivator是默認(rèn)提供Controller實(shí)例的地方是獲取Controller實(shí)例的核心所在,那么我們看看ServiceBasedControllerActivator與DefaultControllerActivator到底有何不同,直接貼出代碼[點(diǎn)擊查看源碼]
public class ServiceBasedControllerActivator : IControllerActivator { public object Create(ControllerContext actionContext) { if (actionContext == null) { throw new ArgumentNullException(nameof(actionContext)); } //獲取Controller類型 var controllerType = actionContext.ActionDescriptor.ControllerTypeInfo.AsType(); //通過Controller類型在容器中獲取實(shí)例 return actionContext.HttpContext.RequestServices.GetRequiredService(controllerType); } public virtual void Release(ControllerContext context, object controller) { } }
相信大家對(duì)上面的代碼一目了然了,和我們上面描述的一樣,將創(chuàng)建Controller實(shí)例的地方改造了在容器中獲取的方式。不知道大家有沒有注意到ServiceBasedControllerActivator的Release的方法居然沒有實(shí)現(xiàn),這并不是我沒有粘貼出來,確實(shí)是沒有代碼,之前我們看到的DefaultControllerActivator可是有調(diào)用Controller的Disposed的方法,這里卻啥也沒有。相信聰明的你已經(jīng)想到了,因?yàn)镃ontroller已經(jīng)托管到了IOC容器中,所以他的生命及其相關(guān)釋放都是由IOC容器完成的,所以這里不需要任何操作。
我們上面還看到了注冊(cè)Controller實(shí)例的時(shí)候使用的是TryAddTransient方法,也就是說每次都會(huì)創(chuàng)建Controller實(shí)例,至于為什么,我想大概是因?yàn)槊看握?qǐng)求都其實(shí)只會(huì)需要一個(gè)Controller實(shí)例,況且EFCore的注冊(cè)方式官方建議也是Scope的,而這里的Scope正是對(duì)應(yīng)的一次Controller請(qǐng)求。在加上自帶的IOC會(huì)提升依賴類型的聲明周期,如果將Controller注冊(cè)為單例的話如果使用了EFCore那么它也會(huì)被提升為單例,這樣會(huì)存在很大的問題。也許正是基于這個(gè)原因默認(rèn)才將Controller注冊(cè)為Transient類型的,當(dāng)然這并不代表只能注冊(cè)為Transient類型的,如果你不使用類似EFCore這種需要作用域?yàn)镾cope的服務(wù)的時(shí)候,而且保證使用的主鍵都可以使用單例的話,完全可以將Controller注冊(cè)為別的生命周期,當(dāng)然這種方式個(gè)人不是很建議。
Controller結(jié)合Autofac
有時(shí)候大家可能會(huì)結(jié)合Autofac一起使用,Autofac確實(shí)是一款非常優(yōu)秀的IOC框架,它它支持屬性和構(gòu)造兩種方式注入,關(guān)于Autofac托管自帶IOC的原理咱們?cè)谥暗奈恼聹\談.Net Core DependencyInjection源碼探究中曾詳細(xì)的講解過,這里咱們就不過多的描述了,咱們今天要說的是Autofac和Controller的結(jié)合。如果你想保持和原有的IOC一致的使用習(xí)慣,即只使用構(gòu)造注入的話,你只需要完成兩步即可
首先將默認(rèn)的IOC容器替換為Autofac,具體操作也非常簡(jiǎn)單,如下所示
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }) //只需要在這里設(shè)置ServiceProviderFactory為AutofacServiceProviderFactory即可 .UseServiceProviderFactory(new AutofacServiceProviderFactory());
然后就是咱們之前說的,要將Controller放入容器中,然后修改生產(chǎn)Controller實(shí)例的ControllerFactory的操作為在容器中獲取,當(dāng)然這一步微軟已經(jīng)為我們封裝了便捷的方法
services.AddMvc().AddControllersAsServices();
只需要通過上面簡(jiǎn)單得兩步,既可以將Controller托管到Autofac容器中。但是,我們說過了Autofac還支持屬性注入,但是默認(rèn)的方式只支持構(gòu)造注入的方式,那么怎么讓Controller支持屬性注入呢?我們還得從最根本的出發(fā),那就是解決Controller實(shí)例存和取的問題
首先為了讓Controller托管到Autofac中并且支持屬性注入,那么就只能使用Autofac的方式去注冊(cè)Controller實(shí)例,具體操作是在Startup類中添加ConfigureContainer方法,然后注冊(cè)Controller并聲明支持屬性注入
public void ConfigureContainer(ContainerBuilder builder) { var controllerBaseType = typeof(ControllerBase); //掃描Controller類 builder.RegisterAssemblyTypes(typeof(Program).Assembly) .Where(t => controllerBaseType.IsAssignableFrom(t) && t != controllerBaseType) //屬性注入 .PropertiesAutowired(); }
其次是解決取的問題,這里我們就不需要AddControllersAsServices方法了,因?yàn)锳ddControllersAsServices解決了Controller實(shí)例在IOC中存和取的問題,但是這里我們只需要解決Controller取得問題說只需要使用ServiceBasedControllerActivator即可,具體操作是
services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());
僅需要在默認(rèn)的狀態(tài)下完成這兩步,既可以解決Controller托管到Autofac中并支持屬性注入的問題,這也是最合理的方式。當(dāng)然如果你使用AddControllersAsServices可是可以實(shí)現(xiàn)相同的效果了,只不過是沒必要將容器重復(fù)的放入容器中了。
總結(jié)
本文我們講述了關(guān)于ASP.NET Core Controller與IOC結(jié)合的問題,我覺得這是有必要讓每個(gè)人都有所了解的知識(shí)點(diǎn),因?yàn)樵谌粘5腤eb開發(fā)中Controller太常用了,知道這個(gè)問題可能會(huì)讓大家在開發(fā)中少走一點(diǎn)彎路,接下來我們來總結(jié)一下本文大致講解的內(nèi)容
- 首先說明了一個(gè)現(xiàn)象,那就是默認(rèn)情況下Controller并不在IOC容器中,我們也通過幾個(gè)示例驗(yàn)證了一下。
- 其次講解了默認(rèn)情況下創(chuàng)造Controller實(shí)例真正的類ActivatorUtilities,并大致講解了ActivatorUtilities的用途。
- 然后我們找到了將Controller托管到IOC容器中的辦法AddControllersAsServices,并探究了它的源碼,了解了它的工作方式。
- 最后我們又演示了如何使用最合理的方式將Controller結(jié)合Autofac一起使用,并且支持屬性注入。
到此這篇關(guān)于ASP.NET Core Controller與IOC結(jié)合問題整理的文章就介紹到這了,更多相關(guān)ASP.NET Core Controller與IOC結(jié)合問題內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
asp.net微信開發(fā)(已關(guān)注用戶管理)
這篇文章主要介紹了asp.net微信開發(fā)中有關(guān)已關(guān)注用戶管理的相關(guān)內(nèi)容,需要的朋友可以參考下2015-11-11asp.net中javascript與后臺(tái)c#交互
這篇文章主要介紹了asp.net中javascript與后臺(tái)c#交互,需要的朋友可以參考下2015-10-10IIS上部署你的ASP.NET?Core?Web?Api項(xiàng)目及Swagger(圖文)
本篇經(jīng)驗(yàn)將和大家介紹如何在IIS上部署ASP.NET?Core項(xiàng)目,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,希望為初學(xué).NET?CORE的童靴入門有所幫助2023-09-09asp.net基礎(chǔ)學(xué)習(xí)之控件的使用方法
這篇文章主要為大家詳細(xì)介紹了asp.net基礎(chǔ)學(xué)習(xí)之控件的使用方法,感興趣的小伙伴們可以參考一下2016-08-08asp.net ListView交替背景顏色實(shí)現(xiàn)代碼
在asp.net中ListView的交替背景顏色實(shí)現(xiàn),GridView的處理得較多,ListView可以這樣實(shí)現(xiàn)。2010-02-02解析如何利用一個(gè)ASP.NET Core應(yīng)用來發(fā)布靜態(tài)文件
本文主要通過一些簡(jiǎn)單的實(shí)例來體驗(yàn)一下如何在一個(gè)ASP.NET Core應(yīng)用中發(fā)布靜態(tài)文件。針對(duì)不同格式的靜態(tài)文件請(qǐng)求的處理,ASP.NET Core為我們提供了三個(gè)中間件,它們將是本系列文章論述的重點(diǎn)。有需要的朋友可以看下2016-12-12.net中自定義錯(cuò)誤頁面的實(shí)現(xiàn)升級(jí)篇
這篇文章主要給大家介紹了關(guān)于.net中自定義錯(cuò)誤頁面實(shí)現(xiàn)的相關(guān)資料,這篇文章是之前的升級(jí)篇,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-06-06