有“规格”不好吗?用于路由代码中指定的控制器/方法

本文关键字:代码 方法 控制器 路由 用于 规格 | 更新日期: 2023-09-27 18:01:45

我正在为ASP.Net设计另一种MVC框架。我对框架的部分目标是尽可能少地使用"魔法"。我唯一的一点反射是绑定的东西,如表单,查询字符串等到一个普通的ol'类(与一些可选的属性忽略,转换等)。因此,我绝对不做类/方法检测。每件事都必须非常明确。我已经经历了大约3次API"形状"的迭代。前两个实现了我的目标,没有魔法,但它是非常冗长,不容易阅读…控制器通常不得不做MVC框架应该做的繁重工作。

那么,现在在第三次迭代中,我正在努力真的努力使它正确。我做的稍微有点争议的事情是路由代码。因为一切都是显式的,不鼓励反射,所以我不能在控制器中搜索某些属性来解析路由。所有内容都必须在路由级别指定。在第一次迭代中我们并没有做到这一点,但它却创造了非常麻烦且冗长的控制器……

现在,我有这个流畅的API来指定路由。它已经超出了我最初的想象,现在作为一种方式来指定控制器的方法能够做什么以及它应该接受什么。

到实际的代码。实现是不相关的。你真正需要知道的唯一一件事是,有很多通用类型涉及。下面是一些路由的快速示例:

var router=new Router(...);
var blog=router.Controller(() => new BlogController());
blog.Handles("/blog/index").With((ctrl) => ctrl.Index());
blog.Handles("/blog/{id}").With((ctrl,model) => ctrl.View(model["id"])).WhereRouteLike((r) => r["id"].IsInteger()); //model defaults to creating a dictionary from route parameters
blog.Handles("/blog/new").UsingFormModel(() => new BlogPost()).With((ctrl, model) => ctrl.NewPost(model)); //here model would be of type BlogPost. Also, could substitue UsingRouteModel, UsingQueryStringModel, etc

还有一些其他的方法可以实现,比如WhereModelIsLike或其他一些对模型进行验证的方法。然而,这种"规范"属于路由层吗?路由层中应该指定哪些限制?什么应该留给控制器来验证?

我让路由层担心太多了吗?

有“规格”不好吗?用于路由代码中指定的控制器/方法

我觉得路由太啰嗦了。我不想为20个控制器写这样的代码。尤其是,因为它真的是重复的。我在这里看到的问题是,即使默认情况也需要详细声明。只有在特殊情况下才需要这些冗长的声明。
它具有表现力和可读性,但您可能需要考虑打包高级功能。

请看下面的规范。这只是针对单个控制器中的单个动作:

blog.Handles("/blog/new")
    .UsingFormModel(() => new BlogPost())
    .With((ctrl, model) => ctrl.NewPost(model))
    .WhereModelIsLike(m => m.Status == PostStatus.New);

稍微减少代码量的一种方法是允许指定一个根文件夹:

var blog=router.Controller(() => new BlogController(), "/blog");
blog.Handles("index").Wi..
blog.Handles("{id}").Wit..
blog.Handles("new").Usin..

另一个减少默认情况下代码的想法是为每个默认操作引入一个接口。控制器需要为支持的操作实现接口:

可能是这样的:

public interface ISupportIndex
{
    void Index();
}
public interface ISupportSingleItem
{
    void View(int id);
}

现在,您可以提供blog.HandlesIndex();, blog.HandlesSingleItem();等方法。
这些方法返回与现有方法相同的内容,因此可以进一步改进结果。
它们可以被设计为扩展方法,只有在控制器实际实现接口时才可用。要实现这一点,router.Controller的返回类型需要是一个协变接口,控制器作为通用参数,即如下所示:

IControllerRoute<out TController>

例如,扩展方法HandlesIndex可以这样实现:

public static IRouteHandler HandlesIndex(
    this IControllerRoute<ISupportIndex> route)
{
    // note: This makes use of the "root" as suggested above:
    // It only specifies "index", not "/someroot/index".
    return route.Handles("index").With(x => x.Index);
}

IControllerRoute<ISupportIndex>上工作,只在控制器实际支持的情况下显示。

blog控制器的路由可以像这样:

blog.HandlesIndex();
blog.HandlesSingleItem();
// Uses short version for models with default constructor:
blog.HandlesNew<BlogPost>().UsingFormModel();
// The version for models without default constructor could look like this:
//blog.HandlesNew<BlogPost>().UsingFormModel(() => new BlogPost(myDependency));

添加验证规则也可以更简洁一点:

blog.HandlesNew<BlogPost>().UsingFormModel()
    .When(m => m.Status == PostStatus.New);

如果规范更复杂,它可以打包在自己的类中实现IModelValidation。这个类现在被使用:

blog.HandlesNew<BlogPost>().UsingFormModel()
    .WithValidator<NewBlogPostValidation>();

我所有的建议都只是让你目前的方法更容易处理的方法,所以我想到目前为止,它并没有真正回答你的实际问题。我现在这样做:

我喜欢我的控制器尽可能干净。在路由上放置验证规则对我来说是一件非常好的事情,因为控制器动作现在可以假设它只使用有效数据调用。我将继续使用这种方法。

是的,依我之见,路由不应该包含关于模型甚至视图的逻辑。

如果你看看现在的轻量级Web框架(Nancy等),路由概念不包括视图链接生成之类的东西。它完全是关于将URI模板映射到控制器。这让ASP失去了很多"魔力"。净实现。

https://github.com/NancyFx/Nancy/wiki/Defining-routes

然而,Nancy方法仍然需要一些"框架"代码来理解哪些路由是可用的。所以,它并不完全符合你的要求。