C# HttpListener的URL映射

20
在下面的代码中,我正在等待对8080端口的任何调用。
public static void Main()
{
    HttpListener listener = new HttpListener();
    listener.Prefixes.Add("http://*:8080/");
    
    listener.Start();
    
    while(isRunning)
    {
        HttpListenerContext ctx = listener.GetContext();
        new Thread(new Worker(ctx).ProcessRequest).Start();
    }
}

有没有可能将特定的 URL 模式映射到不同的行为?我想要实现 REST 风格的服务器,例如对于调用 localhost:8080/person/1,会触发 getPersonHandler(int)

[Mapping("*:8080/person/$id")]
public void getPersonHandler(int id)
{
   // ...
}

Mapping语法只是我希望与我熟悉的JAX-RS库进行类比的愿望。我想在C#中(桌面C#,而不是ASP)做同样的事情。


1
你真的需要重新发明轮子吗?在ASP.NET MVC 4中,Web API可以做到这一点。 - Sam Axe
我需要一个独立的应用程序。 - emesx
4
FYI ASP.NET Web API 可以自托管(不需要 IIS)。 - dcstraw
2
如果ASP.NET Web API可以在可执行文件内自托管,我很想看到这一点!(并不是说它不能,只是我不知道它是否可以)。 - Scott Offen
1
使用OWIN进行自托管:http://www.asp.net/web-api/overview/hosting-aspnet-web-api/use-owin-to-self-host-web-api - Ian Mercer
2个回答

22

你可以不使用属性来获得类似的效果

HttpListener listener = new HttpListener();
listener.Prefixes.Add("http://*:8080/");
listener.Start();
while (true)
{
    HttpListenerContext ctx = listener.GetContext();
    ThreadPool.QueueUserWorkItem((_) =>
    {
        string methodName = ctx.Request.Url.Segments[1].Replace("/", "");
        string[] strParams = ctx.Request.Url
                                .Segments
                                .Skip(2)
                                .Select(s=>s.Replace("/",""))
                                .ToArray();


        var method = this.GetType().GetMethod(methodName);
        object[] @params = method.GetParameters()
                            .Select((p, i) => Convert.ChangeType(strParams[i], p.ParameterType))
                            .ToArray();

        object ret = method.Invoke(this, @params);
        string retstr = JsonConvert.SerializeObject(ret);
    });

使用方法如下:

http://localhost:8080/getPersonHandler/333

如果你真的想使用属性,那么:

HttpListener listener = new HttpListener();
listener.Prefixes.Add("http://*:8080/");
listener.Start();
while (true)
{
    HttpListenerContext ctx = listener.GetContext();
    ThreadPool.QueueUserWorkItem((_) =>
    {
        string methodName = ctx.Request.Url.Segments[1].Replace("/", "");
        string[] strParams = ctx.Request.Url
                                .Segments
                                .Skip(2)
                                .Select(s=>s.Replace("/",""))
                                .ToArray();

        var method = this.GetType()
                            .GetMethods()
                            .Where(mi => mi.GetCustomAttributes(true).Any(attr => attr is Mapping && ((Mapping)attr).Map == methodName))
                            .First();

        object[] @params = method.GetParameters()
                            .Select((p, i) => Convert.ChangeType(strParams[i], p.ParameterType))
                            .ToArray();

        object ret = method.Invoke(this, @params);
        string retstr = JsonConvert.SerializeObject(ret);
    });
}

那么你可以使用 http://localhost:8080/Person/333,并且你的定义将会是:

class Mapping : Attribute
{
    public string Map;
    public Mapping(string s)
    {
        Map = s;
    }
}

[Mapping("Person")]
public void getPersonHandler(int id)
{
    Console.WriteLine("<<<<" + id);
}

这个 @param 是什么样的变量名? - emesx
2
由于“params”是C#中的保留字,因此我使用了“@params”。 - L.B
1
我不知道你可以在变量名中使用 @,谢谢。我喜欢这个答案。非常感谢。 - emesx
最后一件事:你能否按请求类型(GET、PUT等)进行过滤? - emesx
4
ctx.Request.HttpMethod 可翻译为“ctx请求的HTTP方法”。 - L.B

10
如果您正在使用.NET 4.0或更高版本,并且正在寻找可以插入的现有REST服务器解决方案(就像您所描述的那样),您可能需要查看Grapevine。 您可以使用NuGet获取它,project wiki有很多示例代码。此外,它是开源的,因此如果您只想看看如何实现它,您可以在那里看到所有源代码。
您可以通过路径信息(使用正则表达式)和请求方法(GET、POST等)过滤请求。
我是项目的作者,我有一个类似于您描述的需求。使用我在这里和其他地方找到的资源,我构建了Grapevine,以便每当我再次需要时,我都可以拿出一个解决方案(DRY)。

不错的项目 - 我需要将 REST API 放入命令行应用程序中,而 Grapevine 比 C# 中的原始 HttpListener 类更加简洁。 - Shukri Adams

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接