public interface IUrlSerializable
{
Dictionary<string, string> GetQueryParams();
}
并在参数类中实现它:
public class HomeController : Controller
{
public ActionResult HomeAction(Model model) { ... }
}
public class Model : IUrlSerializable
{
public int Id { get; set; }
public string Name { get; set; }
public Dictionary<string, string> GetQueryParams()
{
return new Dictionary<string, string>
{
[nameof(Id)] = Id,
[nameof(Name)] = Name
};
}
}
以及对ActionHelper的相应更改:
public static class ActionHelper<T> where T : Controller
{
...
private static string GetParameter<U>(ParameterInfo info, U value)
{
var serializableValue = value as IUrlSerializable;
if (serializableValue == null)
return GetParameter(info.Name, value.ToString());
return String.Join("&",
serializableValue.GetQueryParams().Select(param => GetParameter(param.Key, param.Value)));
}
private static string GetParameter(string name, string value)
{
return name + '=' + Uri.EscapeDataString(value);
}
}
正如您所看到的,当参数类不实现接口时,它仍然可以回退到ToString()。 用法:
ActionHelper<HomeController>.GetUrl(controller => controller.HomeAction, new Model
{
Id = 1,
Name = "example"
});
internal interface IBaseController { public string Name { get; } }
public class MyController : IBaseController
{
public const string NAME = "My";
// or
public string Name { get => "My"; }
...
}
9条答案
按热度按时间sqserrrh1#
我喜欢使用扩展方法的James' suggestion。只是有一个问题:虽然您使用的是
nameof()
,并且已经消除了幻字符串,但仍然存在一个类型安全的小问题:你仍然在使用字符串。因此,很容易忘记使用扩展方法,或者提供一个任意的无效字符串(例如,错误地键入了控制器的名称)。我认为我们可以通过使用generic extension method for Controller来改进James的建议,其中泛型参数是目标控制器:
现在的用法更加简洁:
nqwrtyyt2#
考虑扩展方法:
然后您可以用途:
6vl6ewon3#
我需要确保
routeValues
被正确处理,而不是总是像querystring
值一样处理。但是,我仍然希望确保操作与控制器匹配。我的解决方案是为
Url.Action
创建扩展重载。我有不同类型的单一参数动作的多载。如果我需要传递
routeValues
...对于带有复杂参数的动作,我没有为它们显式创建重载,需要用控制器类型指定类型以匹配动作定义。
当然,大多数情况下,操作都在同一个控制器中,所以我仍然只使用
nameof
。由于
routeValues
不一定与操作参数匹配,因此该解决方案允许这种灵活性。扩展代码
ny6fqffe4#
到目前为止,我看到的所有解决方案都有一个缺点:虽然它们使更改控制器或操作的名称变得安全,但并不保证这两个实体之间的一致性。您可以从不同的控制器指定操作:
更糟糕的是,该方法不能考虑可以应用于控制器和/或动作以改变路由的许多属性,例如
RouteAttribute
和RoutePrefixAttribute
,因此对基于属性的路由的任何改变可能不被注意。最后,
Url.Action()
本身并不能确保action方法与其构成URL的参数之间的一致性:我的解决方案基于
Expression
和元数据:这可以防止您传递错误的参数来生成URL:
因为它是一个lambda表达式,所以action总是绑定到它的控制器。(而且你也有Intellisense!)一旦选择了action,它就会强制你指定正确类型的所有参数。
给定的代码仍然没有解决路由问题,但是修复它至少是可能的,因为有控制器的
Type.Attributes
和MethodInfo.Attributes
可用。编辑:
正如@CarterMedlin所指出的,非基元类型的操作参数可能没有与查询参数的一对一绑定。目前,这是通过调用
ToString()
来解决的,该ToString()
可能会在参数类中被重写以专门用于此目的。然而,该方法可能并不总是适用,它也不控制参数名称。要解决此问题,可以声明以下接口:
并在参数类中实现它:
以及对
ActionHelper
的相应更改:正如您所看到的,当参数类不实现接口时,它仍然可以回退到
ToString()
。用法:
w46czmvw5#
在Gigi's answer(它为控制器引入了类型安全)的基础上,我又前进了一步。我非常喜欢T4 MVC,但我从不喜欢必须运行T4代。我喜欢代码生成,但它不是MSBuild的本机代码,所以构建服务器很难使用它。
我重新使用了泛型概念并添加了一个
Expression
参数:上述示例调用如下所示:
如果
JobController
没有Index
,你会遇到编译器错误。这可能是这个方法相对于前一个答案的唯一优点--所以这是另一个愚蠢的检查。如果JobController
没有Index
,它会帮助你停止使用JobController
。而且,它会在寻找动作时给你提供智能。我还在这签名上加了一句:
这允许一种更简单的方式为当前控制器输入操作,而不需要指定类型。这两者可以同时使用:
--
有人在评论中问我这个方法是否支持参数。上面的答案是否定的。但是,我很快地创建了一个可以解析参数的版本。这是调整后的方法:
我还没有亲自测试过(但Intellisense会让它看起来像是会编译)。总而言之,程式码会查看方法的所有参数,并建立包含所有参数的ExpandoObject。这些值是从传入的运算式决定的,方法是使用主运算式的原始参数,将每个值当做独立的Lambda运算式呼叫。然后您会编译并叫用运算式,并将结果值存储在ExpandoObject中,然后将结果传递到内置的帮助器中。
px9o7tmv6#
@詹姆斯答上一句:
而是使用字符串扩展方法:返回控制器名称前缀,否则返回传入的参数。
ugmeyewa7#
对于那些想知道如何在ASP.NET核心中实现这一点的人,请尝试以下操作:https://github.com/ivaylokenov/AspNet.Mvc.TypedRouting
5vf7fwbs8#
我用了这样的方法,它很有效:(“控制器”,字符串.空))
f8rj6qna9#
一个简单的常量就可以完成这项工作,或者,如果您愿意,也可以使用一个接口:
无需计算: