windows service宿主web api使用"依赖注入"和“控制反转”的技术实践
前言
自从几年前抛弃wcf,使用web api 来做服务器端开发之后,就不再迷惑了。但是因为本来从事传统行业管理软件开发,一般都以分布式应用开发为主。纯BS还是比较少,于是比较喜欢用windows service来宿主web api。发现这种场景网上文章还是比较少。这次就结合最近的技术尝试(DI、IOC),整体介绍一下这方面的实践。
名词解释
依赖注入:
理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:
●谁依赖于谁:当然是应用程序依赖于IoC容器;
●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。
开发工具和包
IDE: VS2015
Package:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <? xml version="1.0" encoding="utf-8"?> < packages > < package id="Autofac" version="4.4.0" targetFramework="net45" /> < package id="Autofac.Owin" version="4.0.0" targetFramework="net45" /> < package id="Autofac.WebApi2" version="4.0.1" targetFramework="net45" /> < package id="Autofac.WebApi2.Owin" version="4.0.0" targetFramework="net45" /> < package id="EntityFramework" version="6.1.3" targetFramework="net45" /> < package id="EntityFramework.zh-Hans" version="6.1.3" targetFramework="net45" /> < package id="LitJson" version="0.7.0" targetFramework="net45" /> < package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net45" /> < package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net45" /> < package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net45" /> < package id="Microsoft.AspNet.WebApi.OwinSelfHost" version="5.2.3" targetFramework="net45" /> < package id="Microsoft.Owin" version="3.0.1" targetFramework="net45" /> < package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net45" /> < package id="Microsoft.Owin.Hosting" version="3.0.1" targetFramework="net45" /> < package id="Newtonsoft.Json" version="9.0.1" targetFramework="net45" /> < package id="Owin" version="1.0" targetFramework="net45" /> < package id="System.Data.SQLite" version="1.0.104.0" targetFramework="net45" /> < package id="System.Data.SQLite.Core" version="1.0.104.0" targetFramework="net45" /> < package id="System.Data.SQLite.EF6" version="1.0.104.0" targetFramework="net45" /> < package id="System.Data.SQLite.Linq" version="1.0.104.0" targetFramework="net45" /> </ packages > |
targetFramework:net45,注意一下运行时是4.5以上,也是说服务程序必须在win7 sp1以上的操作系统才能运行。
编码细节和要点
1、windows服务宿主web api
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | //protected override public new void OnStart( string [] args) { try { string middleware_url = string .Join( "" , new string [] { "http://" , MiddlewareIP, ":" , MiddlewarePort }); hostObject = WebApp.Start<Startup>(middleware_url); if (hostObject != null ) Com.DataCool.DotNetExpand.LogHelper.Info( "中间件宿主WebApi成功,URL:" + middleware_url); else Com.DataCool.DotNetExpand.LogHelper.Error( "中间件宿主WebApi错误!" ); string result = HttpAPIRequest(); if (! string .IsNullOrEmpty(result)) Com.DataCool.DotNetExpand.LogHelper.Info(result); } catch (Exception ex) { Com.DataCool.DotNetExpand.LogHelper.Error(ex); } IPEndPoint ipeSender = new IPEndPoint(IPAddress.Any, 0); EndPoint epSender = (EndPoint)ipeSender; serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // 首次探测时间5 秒, 间隔侦测时间2 秒 byte [] inValue = new byte [] { 1, 0, 0, 0, 0x88, 0x13, 0, 0, 0xd0, 0x07, 0, 0 }; serverSocket.IOControl(IOControlCode.KeepAliveValues, inValue, null ); IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse(MiddlewareIP), 5880); try { serverSocket.Bind(ipEndPoint); serverSocket.Listen(1024); socketThread = new Thread(ListenClientConnect); socketThread.Start(); } catch (Exception ex) { Com.DataCool.DotNetExpand.LogHelper.Error( "服务启动失败,原因:" + ex.Message); } } |
其实就一句话:hostObject = WebApp.Start<Startup>(middleware_url);这个Startup是用来配置web api的路由规则和实现autofac初始流程的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | public class Startup { public void Configuration(IAppBuilder appBuilder) { HttpConfiguration config = new HttpConfiguration(); //自定义路由 config.Routes.MapHttpRoute( name: "CustomApi" , routeTemplate: "api/{controller}/{action}/{id}" , defaults: new { id = RouteParameter.Optional } ); //规范api格式仅仅支持XML var xmlFormatter = new XmlMediaTypeFormatter(); config.Services.Replace( typeof (IContentNegotiator), new XmlContentNegotiator(xmlFormatter)); var builder = new ContainerBuilder(); //注册本程序集内的ApiControllers builder.RegisterApiControllers(Assembly.GetExecutingAssembly()); //内置日志服务注册 builder.Register(c => new ServiceLog()).As<IServiceLog>().InstancePerRequest(); var iServices = Assembly.Load( "Van.Interface" ); var services = Assembly.Load( "Van.Service" ); //根据名称约定(服务层的接口和实现均以Service结尾),实现服务接口和服务实现的依赖 builder.RegisterAssemblyTypes(iServices, services) .Where(t => t.Name.EndsWith( "Service" )) .AsImplementedInterfaces(); var container = builder.Build(); config.DependencyResolver = new AutofacWebApiDependencyResolver(container); appBuilder.UseAutofacMiddleware(container); appBuilder.UseAutofacWebApi(config); appBuilder.UseWebApi(config); } } |
请看一下注释,友情提示本篇文章的“高潮“部分就在这里,任何解释都是苍白的。哈哈哈...
2、使用注入的接口对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | using Newtonsoft.Json; using System.Web.Http; using Van.Interface; namespace MiddlewareService.controller { /// <summary> /// 合理用药控制器 /// </summary> public class VanController : CoolBaseController { private readonly IServiceLog _logger; private readonly IPersonService _PersonService; public VanController(IServiceLog logService, IPersonService pService) { _logger = logService; _PersonService = pService; } [HttpPost] [HttpGet] public ApiActionResult ClientAnalyzerCheck( string prescriptionInfo) { var ds = MiddlewareServiceSvr.Instance.GetMedDictData(); Com.DataCool.DotNetExpand.LogHelper.Info(ds.GetXml()); var result = new ApiActionResult { Success = false , Message = "操作失败!" + "请求参数:" + prescriptionInfo, Result = null }; Com.DataCool.DotNetExpand.LogHelper.Info(JsonConvert.SerializeObject(result)); return result; } /// <summary> /// 客户端测试用 /// 返回控制器版本号 /// </summary> /// <returns>ApiActionResult 提示成功和正确返回版本号则表示api是可用状态</returns> [HttpPost] [HttpGet] public ApiActionResult GetVersion() { var result = new ApiActionResult { Success = true , Message = "请求客户端IP:" + RequestClientIP + ";操作成功!" + _PersonService.Get( "乔峰" ).Name, Result = "1.0.20170222" }; _logger.Info( "客户端发起请求控制器版本号;服务器回复:" + JsonConvert.SerializeObject(result)); return result; } } } |
控制器里面的接口对象是不需要new的,直接在构造函数里面会被IOC容器自动注入进来。这里提一下依赖关系 控制器应用接口对象所在的程序集和接口所在的程序集,还有实体类所在的程序集,看起来是下图的样子。就是注入的接口对象是在另一个程序集里面。宿主服务和控制器所在的程序是依赖外部注入的接口对象的。
3、windows服务咋调试呢
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | static class Program { /// <summary> /// 应用程序的主入口点。 /// </summary> static void Main( string [] args) { #region 初始化日志组件配置信息 string assemblyFilePath = Assembly.GetExecutingAssembly().Location; string configFilePath = assemblyFilePath + ".config" ; log4net.Config.XmlConfigurator.ConfigureAndWatch( new FileInfo(configFilePath)); #endregion #region 带参数运行 -i安装服务 -u卸载服务 if (args.Length > 0) { AssemblyInstaller myAssemblyInstaller; myAssemblyInstaller = new AssemblyInstaller(); myAssemblyInstaller.UseNewContext = true ; myAssemblyInstaller.Path = System.AppDomain.CurrentDomain.BaseDirectory + "\\" + System.AppDomain.CurrentDomain.FriendlyName; System.Collections.Hashtable mySavedState = new System.Collections.Hashtable(); switch (args[0].ToLower()) { case "-i" : myAssemblyInstaller.Install(mySavedState); myAssemblyInstaller.Commit(mySavedState); myAssemblyInstaller.Dispose(); return ; case "-u" : myAssemblyInstaller.CommandLine = new string [1] { "/u" }; myAssemblyInstaller.Uninstall( null ); myAssemblyInstaller.Dispose(); return ; default : System.Console.WriteLine( "------参数说明------" ); System.Console.WriteLine( "- i 安装服务!" ); System.Console.WriteLine( "- u 卸载服务!" ); System.Console.ReadKey(); return ; } } #endregion //ServiceBase[] ServicesToRun; //ServicesToRun = new ServiceBase[] //{ // new MiddlewareServiceSvr() //}; //ServiceBase.Run(ServicesToRun); new MiddlewareServiceSvr().OnStart( null ); } } |
安装卸载服务可以用自带参数的办法。那么启动服务,停止服务,删除服务呢,用操作系统提供的命令就行了,比如net start ???,net stop ???。这里???是你的服务名。删除: sc delete ???。 调试服务呢?把服务程序集在生成里面设置成“控制台应用程序”,这样可以在运行的时候用Console.WriteLine()...之类的方法来在控制台查看打印的变量或者调试信息了。 把上面的服务标准运行方式改为直接调服务类的OnStart方法来启动服务。
4、截图展示:
作者:数据酷软件
出处:https://www.cnblogs.com/datacool/p/datacool_2017_webapi_owin_autofac.html
关于作者:20年编程从业经验,持续关注MES/ERP/POS/WMS/工业自动化
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明。
联系方式: qq:71008973;wx:6857740733
基于人脸识别的考勤系统 地址: https://gitee.com/afeng124/viewface_attendance_ext
自己开发安卓应用框架 地址: https://gitee.com/afeng124/android-app-frame
WPOS(warehouse+pos) 后台演示地址: http://47.239.106.75:8080/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构