You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# Web服务同一客户端调用次数跟踪方案咨询(非数据库)

针对你的场景,我整理了几个基于C#原生机制的解决方案,不需要依赖数据库,还支持自动过期,适配不同的应用重启情况:

方案1:进程内内存缓存(适用于应用不重启的情况)

如果你的“应用从头重启”只是指方法内部的逻辑重置,而非整个进程/应用域重启,那么用.NET自带的System.Runtime.Caching.MemoryCache是最简便的选择——它是进程内的内存缓存,支持绝对过期和滑动过期,完全满足你的临时存储需求。

代码示例:

using System.Runtime.Caching;

public class dosomething {
    // 使用默认的内存缓存实例
    private static readonly ObjectCache _cache = MemoryCache.Default;
    // 如果需要区分不同客户端,这里的键要换成客户端唯一标识(比如IP、请求ID等)
    private const string ClientCounterKey = "DefaultClientCallCounter";

    public Response somemethod() {
        // 读取计数器,不存在则初始化为0
        int counter = _cache.Get(ClientCounterKey) as int? ?? 0;
        int someLimit = 3; // 自定义的调用次数限制

        if(counter > someLimit) {
            return new Response("go away");
        }

        // 更新计数器,设置10分钟绝对过期,也可以加滑动过期
        var cachePolicy = new CacheItemPolicy {
            AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(10),
            // 可选:滑动过期,每次调用刷新过期时间
            SlidingExpiration = TimeSpan.FromMinutes(5)
        };
        _cache.Set(ClientCounterKey, counter + 1, cachePolicy);

        Response response = new Response(new Link("www.imawebservice.com/dosomething.svc/somemethod"));
        return response;
    }
}

注意:如果每次调用真的会导致整个应用进程重启,这个方案就失效了——因为内存缓存会随进程销毁而清空。

方案2:本地文件存储(适用于进程重启的情况)

如果每次调用都会触发进程重启,那必须用持久化存储。可以用.NET原生的文件操作来实现,配合简单的JSON格式存储键值对和过期时间,同时加锁避免并发冲突。

代码示例:

using System.IO;
using System.Text.Json;

public class dosomething {
    // 缓存文件路径,建议放在应用的临时目录或专用缓存目录
    private const string CacheFile = @"C:\Temp\WebServiceCallCounters.json";
    private const string ClientCounterKey = "DefaultClientCallCounter";
    // 文件锁,确保多线程/多进程写入时的线程安全
    private static readonly object _fileLock = new object();

    public Response somemethod() {
        // 读取并解析缓存文件
        var cacheData = new Dictionary<string, CacheEntry>();
        lock(_fileLock) {
            if(File.Exists(CacheFile)) {
                string jsonContent = File.ReadAllText(CacheFile);
                cacheData = JsonSerializer.Deserialize<Dictionary<string, CacheEntry>>(jsonContent) ?? new Dictionary<string, CacheEntry>();
            }
        }

        // 清理过期的缓存项
        DateTime now = DateTime.Now;
        List<string> expiredKeys = cacheData.Where(kv => kv.Value.ExpiresAt < now).Select(kv => kv.Key).ToList();
        foreach(string key in expiredKeys) {
            cacheData.Remove(key);
        }

        // 获取当前计数器值
        int counter = cacheData.TryGetValue(ClientCounterKey, out CacheEntry entry) ? entry.CounterValue : 0;
        int someLimit = 3;

        if(counter > someLimit) {
            return new Response("go away");
        }

        // 更新计数器,设置10分钟过期
        cacheData[ClientCounterKey] = new CacheEntry {
            CounterValue = counter + 1,
            ExpiresAt = now.AddMinutes(10)
        };

        // 写入更新后的缓存数据到文件
        lock(_fileLock) {
            string jsonOutput = JsonSerializer.Serialize(cacheData);
            File.WriteAllText(CacheFile, jsonOutput);
        }

        Response response = new Response(new Link("www.imawebservice.com/dosomething.svc/somemethod"));
        return response;
    }

    // 辅助类,存储计数器值和过期时间
    private class CacheEntry {
        public int CounterValue { get; set; }
        public DateTime ExpiresAt { get; set; }
    }
}

注意:要确保应用对目标文件路径有读写权限;如果是多实例部署,多个进程读写同一文件可能会有性能问题,需要额外的分布式同步机制。

方案3:Windows注册表存储(仅适用于Windows环境,进程重启的情况)

如果你的服务运行在Windows环境下,可以用.NET原生的Microsoft.Win32.Registry类来存储计数器,同样能实现过期逻辑,不需要额外的文件操作。

代码示例:

using Microsoft.Win32;

public class dosomething {
    // 注册表路径,建议放在当前用户的注册表分支下(权限要求更低)
    private const string RegistrySubPath = @"Software\MyWebService\CallCounters";
    private const string CounterValueName = "DefaultClientCounter";
    private const string ExpirationValueName = "DefaultClientCounter_Expires";

    public Response somemethod() {
        int counter = 0;
        DateTime expiresAt = DateTime.MinValue;

        // 读取注册表中的计数器和过期时间
        using(RegistryKey key = Registry.CurrentUser.OpenSubKey(RegistrySubPath, true)) {
            if(key != null) {
                counter = (int?)key.GetValue(CounterValueName, 0) ?? 0;
                string expStr = key.GetValue(ExpirationValueName, DateTime.MinValue.ToString()).ToString();
                DateTime.TryParse(expStr, out expiresAt);
            }
        }

        // 检查是否过期,过期则重置计数器
        if(DateTime.Now > expiresAt && expiresAt != DateTime.MinValue) {
            counter = 0;
        }

        int someLimit = 3;
        if(counter > someLimit) {
            return new Response("go away");
        }

        // 更新计数器和过期时间(10分钟)
        DateTime newExpiresAt = DateTime.Now.AddMinutes(10);
        using(RegistryKey key = Registry.CurrentUser.CreateSubKey(RegistrySubPath)) {
            key?.SetValue(CounterValueName, counter + 1);
            key?.SetValue(ExpirationValueName, newExpiresAt.ToString());
        }

        Response response = new Response(new Link("www.imawebservice.com/dosomething.svc/somemethod"));
        return response;
    }
}

注意:这个方案仅支持Windows系统,且需要应用有注册表的读写权限;非Windows环境请用方案2。

额外提示

  • 客户端区分:如果需要跟踪多个客户端的调用次数,你需要获取客户端的唯一标识(比如从HttpContext中读取客户端IP、请求头中的唯一ID等),将其作为缓存的键,避免不同客户端的计数器相互干扰。
  • 并发安全:所有涉及持久化的操作都要加锁,防止多线程/多进程同时读写导致的数据错乱。

内容的提问来源于stack exchange,提问作者Lixyna

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 06:47:15