所有分类
  • 所有分类
  • 游戏源码
  • 网站源码
  • 单机游戏
  • 游戏素材
  • 搭建教程
  • 精品工具

.NET Core自定义服务怎么实现?超详细步骤教你从0到1

.NET Core自定义服务怎么实现?超详细步骤教你从0到1 一

文章目录CloseOpen

第一步:先把自定义服务的“骨架”搭对——接口+实现类

很多新手一开始会跳过“接口”,直接写一个XXService实现功能,比如直接写AliyunSmsService发送短信。但去年帮朋友做电商项目时,他就踩了这个坑:他把支付逻辑直接写在WeChatPaymentService里,后来要切换到支付宝支付,改代码改了整整三天——因为所有调用支付的地方都直接引用了WeChatPaymentService,每处都要替换成AlipayPaymentService。那三天他跟我说:“早知道写个接口就好了。”

接口的作用其实就一个:解耦——把“要做什么”和“具体怎么做”分开。比如发送短信的服务,接口ISmsService只定义“发送短信”这个行为(SendSmsAsync方法),而AliyunSmsServiceTencentSmsService是具体的实现。以后要换短信服务商,只需要新增一个实现类,再改下注册的地方,其他调用ISmsService的代码完全不用动。

给你举个具体的“搭骨架”例子,比如做一个发送短信的服务:

  • 写接口:定义服务的“能力边界”(要做什么)
  • public interface ISmsService
    

    {

    // 发送短信的方法,参数是手机号和内容,返回是否成功

    Task SendSmsAsync(string phoneNumber, string content);

    }

  • 写实现类:填充“具体怎么做”的逻辑(比如调用阿里云短信API)
  • public class AliyunSmsService ISmsService
    

    {

    // 从配置文件读取阿里云的AccessKey(后面讲怎么注入配置)

    private readonly string _accessKey;

    private readonly string _accessSecret;

    // 通过构造函数注入IConfiguration(.NET Core自带的配置服务)

    public AliyunSmsService(IConfiguration configuration)

    {

    _accessKey = configuration["Aliyun:Sms:AccessKey"];

    _accessSecret = configuration["Aliyun:Sms:AccessSecret"];

    }

    public async Task SendSmsAsync(string phoneNumber, string content)

    {

    // 初始化阿里云短信客户端

    var client = new DefaultAcsClient(

    new Credential(_accessKey, _accessSecret),

    "cn-hangzhou"

    );

    // 构造发送短信的请求(阿里云SDK的标准写法)

    var request = new SendSmsRequest

    {

    PhoneNumbers = phoneNumber,

    SignName = "你的短信签名(要在阿里云备案)",

    TemplateCode = "你的短信模板ID",

    TemplateParam = JsonConvert.SerializeObject(new { content }) // 模板参数

    };

    // 调用API发送短信

    var response = await client.GetAcsResponseAsync(request);

    // 返回是否成功(阿里云返回Code为"OK"表示成功)

    return response.Code.Equals("OK", StringComparison.OrdinalIgnoreCase);

    }

    }

    这里要避的坑:别把接口写得太“胖”——比如不要在ISmsService里加SendEmail(发邮件)、Log(日志)的方法。去年有个网友给我看他的代码,IService接口里塞了5个不同功能的方法,结果要拆分服务时,改得头都大了。接口一定要“单一职责”:一个接口只负责一件事。

    第二步:把服务“装”进.NET Core的“容器”里——注册的3种方式

    写好接口和实现类后,得把服务“放”进.NET Core的依赖注入容器(可以理解成一个“服务仓库”)里,不然程序找不到你的服务。注册服务就一句话,但关键是选对生命周期——这是新手最常踩的坑。

    先搞懂3种生命周期的区别

    .NET Core提供了3种服务生命周期,每种对应不同的场景,选错了会出大问题:

    生命周期类型 实例创建规则 适用场景 踩坑案例
    Singleton(单例) 整个应用一生只创建1个实例,所有请求共享 无状态、全局通用的服务(比如日志、配置) 把日志服务注册成Transient,导致内存暴涨(每请求造1个实例)
    Scoped(作用域) 每个HTTP请求创建1个实例,请求结束后销毁 与请求相关的有状态服务(比如用户会话、购物车) 把购物车服务注册成Singleton,导致不同用户的购物车数据串了
    Transient(瞬时) 每次请求服务时都创建新实例 无状态、轻量级的服务(比如短信、邮件发送) 把短信服务注册成Singleton,导致多次发送短信的参数串了

    注册服务的具体操作

    在.NET 6+项目里,注册服务是在Program.cs里的builder.Services对象上操作(.NET 5及以下是在Startup.csConfigureServices方法里)。比如注册ISmsServiceAliyunSmsService

  • 用Transient生命周期(适合短信服务):
  •  builder.Services.AddTransient();
    

  • 用Scoped生命周期(比如购物车服务):
  • csharp

    builder.Services.AddScoped();

  • 用Singleton生命周期(比如日志服务):
  • csharp

    builder.Services.AddSingleton();

    最容易踩的坑:注册时漏写“接口”——比如写成AddTransient(),结果在控制器里注入ISmsService时,会报“无法解析服务类型”的错。去年帮朋友调过这个问题,他说“我明明注册了啊”,结果一看,注册的是具体类,不是接口——容器里没有ISmsService的映射关系,自然找不到。

    第三步:用对方法“拿”服务——依赖注入的正确姿势

    服务注册好后,接下来要“拿”出来用。.NET Core推荐的方式是依赖注入(DI)——不用你自己

    new实例,容器会自动把服务“递”到你手里。

  • 最推荐的方式:构造函数注入
  • 构造函数注入是.NET Core的“官方首选”,因为简单、安全,而且容器会自动处理所有依赖。比如控制器里要发送短信:

    csharp

    [ApiController]

    [Route(“api/[controller]”)]

    public class SmsController ControllerBase

    {

    // 声明一个ISmsService的字段,用来存注入的服务

    private readonly ISmsService _smsService;

    // 构造函数注入:容器会自动把ISmsService的实例传进来

    public SmsController(ISmsService smsService)

    {

    _smsService = smsService;

    }

    [HttpPost(“send”)]

    public async Task SendSms(string phone, string content)

    {

    // 直接调用服务的方法,不用自己new

    var result = await _smsService.SendSmsAsync(phone, content);

    if (result)

    {

    return Ok(“短信发送成功”);

    }

    return BadRequest(“短信发送失败,请检查参数”);

    }

    }

    ### 
  • 服务类之间的依赖:同样用构造函数注入
  • 如果你的

    OrderService需要用ISmsService发送订单通知,直接在OrderService的构造函数里加ISmsService参数就行:

    csharp

    public class OrderService

    {

    private readonly ISmsService _smsService;

    public OrderService(ISmsService smsService)

    {

    _smsService = smsService;

    }

    public async Task CreateOrderAsync(Order order)

    {

    //

  • 保存订单到数据库(省略逻辑)
  • //

  • 发送短信通知用户
  • await _smsService.SendSmsAsync(

    order.UserPhone,

    $”您的订单{order.Id}已创建,预计3天内送达”

    );

    }

    }

    ### 
  • 特殊场景:从IServiceProvider手动获取服务
  • 有些场景下没法用构造函数注入——比如后台任务、中间件里,这时候可以用

    IServiceProvider(服务容器的入口)手动拿服务。比如一个后台定时发送短信的任务:

    csharp

    public class SmsBackgroundService BackgroundService

    {

    private readonly IServiceProvider _serviceProvider;

    public SmsBackgroundService(IServiceProvider serviceProvider)

    {

    _serviceProvider = serviceProvider;

    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)

    {

    while (!stoppingToken.IsCancellationRequested)

    {

    // 创建一个作用域:避免Singleton服务持有Scoped服务(会导致内存泄漏)

    using (var scope = _serviceProvider.CreateScope())

    {

    // 从作用域里拿ISmsService

    var smsService = scope.ServiceProvider.GetRequiredService();

    // 发送测试短信(实际场景可以从数据库读待发送的短信)

    await smsService.SendSmsAsync(“138XXXX1234”, “这是一条后台任务发送的短信”);

    }

    // 每隔10分钟执行一次

    await Task.Delay(TimeSpan.FromMinutes(10), stoppingToken);

    }

    }

    }

    这里要避的坑:别在Singleton服务里注入Scoped服务。比如把UserService注册成Singleton,里面注入了ScopedDbContext,结果不同用户的请求用的是同一个DbContext,导致用户数据串了——去年帮朋友调过这个错,改成Scoped后直接解决。

    这些步骤你学会了吗?其实自定义服务实现真的不难,关键是把“接口+实现类”的骨架搭对、选对生命周期、用对注入方式。我去年帮朋友做项目时,按照这三步做,从来没再出过错。你要是照着做遇到问题,欢迎在评论区告诉我,我帮你调。


    自定义服务为什么一定要写接口?直接写实现类不行吗?

    当然不是不行,但直接写实现类会踩“解耦”的大坑。就像我去年帮朋友做电商项目时,他把支付逻辑直接写在WeChatPaymentService里,后来要切换到支付宝支付,所有调用支付的地方都得手动替换成AlipayPaymentService,改了整整三天。接口的核心作用就是把“要做什么”和“具体怎么做”分开——比如ISmsService定义“发送短信”的行为,AliyunSmsService是具体实现,以后换服务商只需要加新的实现类、改下注册代码,其他调用ISmsService的地方完全不用动。直接写实现类的话,后期扩展或修改的成本会高到让你崩溃。

    三种服务生命周期(Singleton/Scoped/Transient)怎么选?选错了会有什么问题?

    得结合服务的“状态”和“使用场景”来选:Singleton适合无状态、全局通用的服务(比如日志、配置),整个应用只创建一个实例,选错了比如把购物车服务注册成Singleton,会导致不同用户的购物车数据串了;Scoped对应每个HTTP请求一个实例,适合和请求相关的有状态服务(比如用户会话、购物车),选错了比如把DbContext注册成Transient,会频繁创建销毁实例,影响性能;Transient是每次请求服务时都新建实例,适合轻量级、无状态的服务(比如短信、邮件发送),选错了比如把短信服务注册成Singleton,会导致多次发送的参数串了。我去年帮三个朋友调过这类问题, 下来“看状态、看场景”是关键。

    注册服务时漏写接口了怎么办?比如只写了AddTransient()没加ISmsService

    这会导致.NET Core的依赖注入容器“找不到”ISmsService的映射关系——容器里只存了AliyunSmsService的实例,但你在控制器里要注入ISmsService时,容器不知道“ISmsService对应的是AliyunSmsService”,就会报“无法解析服务类型”的错。解决办法很简单:把注册代码改成AddTransient()(根据你需要的生命周期选AddSingleton/AddScoped),这样容器就会建立“ISmsService→AliyunSmsService”的映射,注入的时候就能正确找到实例了。

    构造函数注入和从IServiceProvider手动拿服务,哪种更推荐?

    官方最推荐的是“构造函数注入”,因为它简单、安全,而且容器会自动处理所有依赖——你不用自己new实例,也不用管服务的生命周期。比如控制器里要用到ISmsService,直接在构造函数里加参数,容器会自动把实例传进来。而从IServiceProvider手动拿服务(比如后台任务里用scope.ServiceProvider.GetRequiredService()),只适合“特殊场景”:比如后台定时任务、中间件这类没法用构造函数注入的情况。但要注意,手动拿的时候一定要创建“作用域”(用CreateScope()),不然容易导致内存泄漏或者状态串了。

    为什么Singleton服务里不能注入Scoped服务?会有什么后果?

    因为Singleton服务是“整个应用一生只创建一个实例”,而Scoped服务是“每个HTTP请求创建一个实例”。如果在Singleton里注入Scoped服务(比如在Singleton的LogService里注入Scoped的DbContext),那么这个DbContext实例会跟着LogService一起“活”到应用结束——比如用户A的请求创建了DbContext,用户B的请求还在用同一个,就会导致用户数据串了(比如B能看到A的日志),或者内存越用越多(因为DbContext没被及时销毁)。我去年帮朋友调过这个问题,他把UserService注册成Singleton,里面注入了Scoped的DbContext,结果用户数据串了,改成Scoped后直接解决。所以一定要记住:Singleton里只能注入Singleton服务,不能碰Scoped或Transient。

    原文链接:https://www.mayiym.com/52499.html,转载请注明出处。
    0
    显示验证码
    没有账号?注册  忘记密码?

    社交账号快速登录

    微信扫一扫关注
    如已关注,请回复“登录”二字获取验证码