在ASP.NET MVC中的模型中调用UrlHelper

166
我需要在ASP.NET MVC模型中生成一些URL。我想调用类似于UrlHelper.Action()的方法来使用路由生成URL。我不介意填写通常的空白,比如主机名、协议等。
有没有可以调用的方法?有没有构建UrlHelper的方法?

1
我本想这样做,但请注意Url.Action将生成一个相对URL。确保这正是你想要的。 - Vivian River
8个回答

284

在任何 ASP.NET 应用程序中,有一个有用的提示:您可以获取当前 HttpContext 的引用。

HttpContext.Current

这是从System.Web中派生出来的。因此,以下内容在ASP.NET MVC应用程序中的任何地方都可以正常工作:

UrlHelper url = new UrlHelper(HttpContext.Current.Request.RequestContext);
url.Action("ContactUs"); // Will output the proper link according to routing info

例子:

public class MyModel
{
    public int ID { get; private set; }
    public string Link
    {
        get
        {
            UrlHelper url = new UrlHelper(HttpContext.Current.Request.RequestContext);
            return url.Action("ViewAction", "MyModelController", new { id = this.ID });
        }
    }

    public MyModel(int id)
    {
        this.ID = id;
    }
}

在创建了一个 MyModel 对象后,调用其 Link 属性会返回基于 Global.asax 中路由信息的用于查看该模型的有效 URL。


你确定有HttpContext.Current.Request.RequestContext吗?HttpContext.Current.Request似乎没有RequestContext。 - pupeno
1
很奇怪。我刚刚测试了这个解决方案,它完美地工作了。我正在运行ASP.NET MVC 2 Preview 2,但我认为这适用于所有版本。不确定为什么您的情况无法工作。您是否在MVC项目之外创建类?还要确保using同时包括System.WebSystem.Web.Mvc - Omar
我正在进行一个ASP.NET MVC 1项目,我想到可能是缺少了using语句,但实际上我已经添加了这两个using语句。 - pupeno
不太确定为什么它没有显示出来。如果有人能确认在ASP.NET MVC 1中不存在这个问题,那就太好了。我只有一台安装了VS2010和MVC 2的机器。如果您感兴趣,可以尝试使用MVC RC 2 http://haacked.com/archive/2009/12/16/aspnetmvc-2-rc.aspx - Omar
6
请注意,Request.RequestContext 在 .NET4+ 中得到支持。 - h--n
显示剩余3条评论

70

我喜欢Omar的答案,但对我没用。只是为了记录,这是我现在正在使用的解决方案:

var httpContext = HttpContext.Current;

if (httpContext == null) {
  var request = new HttpRequest("/", "http://example.com", "");
  var response = new HttpResponse(new StringWriter());
  httpContext = new HttpContext(request, response);
}

var httpContextBase = new HttpContextWrapper(httpContext);
var routeData = new RouteData();
var requestContext = new RequestContext(httpContextBase, routeData);

return new UrlHelper(requestContext);

它包含了我的网站的URL。我已经将其删除了。 - pupeno
4
考虑到UrlHelper类依赖于请求上下文(以及HTTP上下文),手动构建这些上下文对象可能会产生意外的结果。如果HttpContext.Current为空且您使用此方法,则我建议谨慎操作。 - Sean
4
请注意此答案 - 虚拟的RequestContext会导致UrlHelper始终返回空字符串。 - gknicker
有人知道如何在.NET Core中实现这个吗? - Johannes Mols

51

可以通过以下方式在控制器操作中构建UrlHelper:

 var url = new UrlHelper(this.ControllerContext.RequestContext);
 url.Action(...);

在控制器之外,可以通过从RouteTable.Routes RouteData创建一个RequestContext来构建UrlHelper。

HttpContextWrapper httpContextWrapper = new HttpContextWrapper(System.Web.HttpContext.Current);
UrlHelper urlHelper = new UrlHelper(new RequestContext(httpContextWrapper, RouteTable.Routes.GetRouteData(httpContextWrapper)));

(基于Brian的回答,稍作代码纠正后。)


2
但是我模型中没有控制器。 - pupeno
3
var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext); 翻译为中文: - bradlis7
它在MVC 5中有效,但@Omar在2010年的答案中也有它,所以它可能已经是当时最新版本的一部分了。 - bradlis7
哦,果然没错。很酷。 - Nathan Taylor
1
无法工作,因为this.ControllerContext.RequestContext是一个HttpRequestContext,而UrlHelper的构造函数需要一个RequestContext。这两个类没有关联。 - Florian Winter
显示剩余2条评论

8

可以实例化它。您可以执行类似以下内容的操作:

var ctx = new HttpContextWrapper(HttpContext.Current);
UrlHelper helper = new UrlHelper(
   new RequestContext(ctx,
   RouteTable.Routes.GetRouteData(ctx));

RouteTable.Routes是一个静态属性,所以你不用担心;要获取HttpContextBase引用,可以使用HttpContextWrapper来获取对HttpContext的引用,然后HttpContext就可以提供它。


这个不会起作用,虽然很接近。请看下面我的答案。 - Nathan Taylor

4

在尝试了所有其他答案后,我最终得到了

$"/api/Things/Action/{id}"

讨厌者总是会讨厌 ¯\_(ツ)_/¯

0
之前的回答并没有帮到我。我的做法是在 Home 控制器中创建一个与 UrlHelper 功能相同的动作。
    [HttpPost]
    [Route("format-url")]
    public ActionResult FormatUrl()
    {
        string action = null;
        string controller = null;
        string protocol = null;
        dynamic parameters = null;

        foreach (var key in this.Request.Form.AllKeys)
        {
            var value = this.Request.Form[key];
            if (key.Similar("action"))
            {
                action = value;
            }
            else if (key.Similar("controller"))
            {
                controller = value;
            }
            else if (key.Similar("protocol"))
            {
                protocol = value;
            }
            else if (key.Similar("parameters"))
            {
                JObject jObject = JsonConvert.DeserializeObject<dynamic>(value);

                var dict = new Dictionary<string, object>();
                foreach (var item in jObject)
                {
                    dict[item.Key] = item.Value;
                }

                parameters = AnonymousType.FromDictToAnonymousObj(dict);
            }
        }

        if (string.IsNullOrEmpty(action))
        {
            return new ContentResult { Content = string.Empty };
        }

        int flag = 1;
        if (!string.IsNullOrEmpty(controller))
        {
            flag |= 2;
        }

        if (!string.IsNullOrEmpty(protocol))
        {
            flag |= 4;
        }

        if (parameters != null)
        {
            flag |= 8;
        }

        var url = string.Empty;
        switch (flag)
        {
            case 1: url = this.Url.Action(action); break;
            case 3: url = this.Url.Action(action, controller); break;
            case 7: url = this.Url.Action(action, controller, protocol); break;
            case 9: url = this.Url.Action(action, parameters); break;
            case 11: url = this.Url.Action(action, controller, parameters); break;
            case 15: url = this.Url.Action(action, controller, parameters, protocol); break;
        }

        return new ContentResult { Content = url };
    }

作为一个操作,您可以从任何地方请求它,甚至在中心内部:

            var postData = "action=your-action&controller=your-controller";

            // Add, for example, an id parameter of type integer
            var json = "{\"id\":3}";
            postData += $"&parameters={json}";

            var data = Encoding.ASCII.GetBytes(postData);

#if DEBUG
            var url = $"https://localhost:44301/format-url";
#else
            var url = $"https://your-domain-name/format-url";
#endif

            var request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = "POST";
            request.ContentType = "application/text/plain";
            request.ContentLength = data.Length;

            using (var stream = request.GetRequestStream())
            {
                stream.Write(data, 0, data.Length);
            }

            var response = (HttpWebResponse)request.GetResponse();
            var link = new StreamReader(stream: response.GetResponseStream()).ReadToEnd();

你可以在 这里 获取匿名类型的源代码。

0

我曾试图在页面内(控制器之外)执行类似的操作。

UrlHelper并没有像Pablo的答案那样轻松地让我构建它,但是我想起了一个旧技巧来有效地完成同样的事情:

string ResolveUrl(string pathWithTilde)

-30

我想你要找的是这个:

Url.Action("ActionName", "ControllerName");

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