65.9K
CodeProject 正在变化。 阅读更多。
Home

.NET REST 服务

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.98/5 (15投票s)

2012年5月18日

CPOL

4分钟阅读

viewsIcon

46288

downloadIcon

674

自安装的版本跟踪 REST 服务,用于构建

引言

这个项目虽然小,但雄心勃勃;

  • REST Web 服务 (套接字监听器/HTTP 解析器)
  • 自安装的 Windows 服务
  • 12.5 KB
  • 做了一些有用的事情 (获取/设置/递增/持久化版本号)

背景

我最初的目的是创建一个服务来集中管理构建版本号,但最终却成为一个关于 .NET 中轻量级 RESTful 服务实现的有趣研究。在构建环境中,版本递增通常通过手动编写脚本、自定义操作或工作流活动来解决。这通常不是一个构建解决方案中最优雅的部分。本项目构建了一个更优雅的 REST 服务,它直接从构建过程中调用 RESTful 服务,该服务直接递增和获取任意数量的构建版本。例如,打开你的网页浏览器并输入 URL

https://:2313/LodeRunner?v=+

..将返回 

3.0.0.1

你最喜欢的 LodeRunner 仿制项目构建的版本。

自安装  

这项服务的自安装功能实际上源自这篇文章... 非常感谢。关键在于你需要定义一个服务和一个安装程序,然后根据需要处理传入的参数 (参见Versioner.Install.csVersioner.Services.cs)。

安装程序

[RunInstaller(true)]
public class Installer : inst.Installer
{
    public Installer()
    {
        Installers.Add(new ServiceProcessInstaller() { Account = ServiceAccount.LocalSystem });
        Installers.Add(new ServiceInstaller()
        {
            DelayedAutoStart = false,
            StartType = ServiceStartMode.Automatic,
            Description = "Tracks version numbers for build resources",
            ServiceName = Service.Name,
            DisplayName = "Versioner Service",
        });
    }
} 

这是服务管理器所必需的,以便提供你从服务管理器中看到的那些基本信息。服务本身在 Versioner.Service 中。这实际上就是服务的“主入口”。稍后在讨论它是如何工作的时,我们会详细讨论它。

SimplyListen

正确设置套接字有很多细节,所以我创建了一个助手。该类封装了监听的基本概念,并在套接字连接时提供一个 Action<T> 回调。

public class SimplyListen : IDisposable
{
    private Action<Socket> _onConnect;
    private SimplyListen Listen(int port, Action<Socket> onConnect)
    {
        _onConnect = onConnect;
 
        // Create the listener socket in this machines IP address
        _socketLast = new Socket(AddressFamily.InterNetwork,
                          SocketType.Stream, ProtocolType.Tcp);
 
        _socketLast.Bind(new IPEndPoint(LocalHost, port));
        //listener.Bind( new IPEndPoint( IPAddress.Loopback, 399 ) );
        // For use with localhost 127.0.0.1
        _socketLast.Listen(10);
 
        // Setup a callback to be notified of connection requests
        _socketLast.BeginAccept(new AsyncCallback(OnConnectRequest), _socketLast);
 
        return this;
    }
...
}

这里的关键调用是 OnConnectRequest。这是套接字层在需要响应传入套接字连接时调用的回调方法。理解这段代码的关键在于认识到有两个套接字...一个是“监听”的、一直保持打开的套接字,另一个是客户端连接的、你可以接收传入字节的套接字。_socketLast.EndAcccept() 调用获取内部套接字并将其传递给我们的 _onConnect Action<T>。从那里,调用者可以只处理连接方面的问题,而无需处理套接字的所有其他复杂性。

private Socket _socketLast = null;
 
private void OnConnectRequest(IAsyncResult ar)
{
    lock (_socketLast)
    {
        _socketLast = ar.AsyncState as Socket;
    }
    _onConnect(_socketLast.EndAccept(ar));
    _socketLast.BeginAccept(new AsyncCallback(OnConnectRequest), _socketLast);
}

版本控制是啥?!

所以所有这些“装饰”都围绕着真正的代码,也就是持久化版本跟踪机制。这个想法是我们在磁盘上存储一个逗号分隔的版本字符串列表。它看起来像这样: 

bruce,1.0.3.1
bob,3.0.0.3
LoadRunner,1.0.0.1 

我使用一个非常基础的 LINQ 命令将其转换为一个 Dictionary,以便在内存中方便地使用...

Versions = !File.Exists (filename) ? new Dictionary<string, string>() :
    File.ReadAllLines(filename).Select(l =>
        l.Split(',')).Where(v=>v.Length==2).ToDictionary(k => k[0], v => v[1])

保存它也使用了类似的 LINQ 命令。

File.WriteAllLines(filename,
    Versions.Select(v=>string.Format("{0},{1}", v.Key, v.Value)).ToArray());

幕后工作:将所有内容整合在一起

URL 参数中的命令可以是你想设置的版本,或者一个或多个加号 ('+'),这意味着“递增”版本号。加号的数量表示要递增的版本号的位置。一个加号表示递增最后一个数字,两个表示递增倒数第二个,依此类推。我将其编码为,版本号中的分隔符数量没有限制,4 是标准的,但我特意让实现更加健壮,以便任何数量都可以正常工作。

private static string Increment(string name, string version)
{
    string response = version;
    if (!string.IsNullOrEmpty(version))
    {
        int plusCount = version.Count(c => '+' == c);

        if (plusCount > 0)
        {
            if (!vers.Versions.TryGetValue(name, out response))
                response = "1.0.0.0";

            response = Versioner.Increment(response, plusCount);
        }
    }
    return response;
}

这使用了 Versioner 中的一个静态方法来执行实际的数字递增。它将版本字符串分割成一个数组,解析为整数,在正确的位置递增,然后重新连接。

public static string Increment(string response, int incIndex)
{
    var ints = response.Split('.').Select(i => int.Parse(i)).ToArray();
    int pos = ints.Length - incIndex;

    if (pos < 0)
        return response;

    ints[pos] = ints[pos]+1;
    while (++pos < ints.Length)
        ints[pos] = 0;

    return string.Join(".", ints.Select(i => i.ToString()));
}

如果我们已经确定了我们的版本号,现在我们需要更新内存中的字典并保存它。此代码还处理我们只是请求版本号的情况 (根本没有版本参数)。那就是“TryGet”。

private static string UpdateVersions(string name, string response)
{
    if (!string.IsNullOrEmpty(response))
    {
        if (vers.Versions.ContainsKey(name))
            vers.Versions[name] = response;
        else
            vers.Versions.Add(name, response);
 
        vers.Save(_versionFilename);
    }
    else if (!vers.Versions.TryGetValue(name, out response))
    {
        response = string.Empty;
    }
 
    return response;
}

最后,这是将所有内容包装在一起的代码。它位于响应连接尝试的服务处理程序的 lambda 中。 

  • ReadHttpRequestBuffer() 从套接字读取并返回名称和版本参数。
  • Increment() 根据版本参数进行请求。
  • UpdateVersions() 更新内存中的字典并将版本持久化到磁盘。
  • 将响应作为字节数组发送。
string name, version;
if (SimplyListen.ReadHttpRequestBuffer(s, out name, out version))
{
    var response = UpdateVersions(name, Increment(name, version));
    // Convert to byte array and send.

    var byteDateLine = System.Text.Encoding.ASCII.GetBytes(response);

    s.Send(byteDateLine, byteDateLine.Length, 0);
}

使用方法  

安装

  • 复制到你想存放的位置。
  • 以 **管理员身份** 启动命令提示符。
  • 切换到复制的目录。
  • 运行...
Versioner.exe /i
 
Running a transacted installation.
 
Beginning the Install phase of the installation.
.
.

安装完成后,你可以打开任何浏览器并输入此内容:

https://:2313/LodeRunner?v=1.0.0.0 
 1.0.0.0

https://:2313/LodeRunner?v=++
 1.0.1.0 

URL 的末尾是项目的名称,'v' 参数有两种模式:

  • 如果指定一个由点分隔的整数列表,它将设置该实体的版本号。
  • 如果指定任意数量的 '+' 字符,它将设置版本号中的相应索引...

示例:'v=+' 递增 (并保存) "构建" 数字,'v=+++' 递增次要版本号。

未来:在实际构建中使用

你需要自己实现如何将这个功能集成到你的构建中,但在接下来的几周里,我可能会在这篇文章的第二部分中更新 TFS 构建工作流中的构建号。    

历史

  • 12 年 5 月 17 日 - 创建  
  • 12 年 5 月 19 日 - 重写了引言,使其更具普遍性
  • 12 年 5 月 25 日 - 修复了本地主机中的一个 bug 
© . All rights reserved.