.NET Core 通用控制器的路由覆盖

5

我正在编写一个Rest框架,并尝试找出如何允许用户为通用控制器创建自定义名称。我像这样注册我的通用控制器:

public class GenericControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature>
{
    public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
    {
        foreach (var entityConfig in _entityConfigurations)
        {
            var entityType = entityConfig.Type;
            var typeName = entityType.Name + "Controller";
            if (!feature.Controllers.Any(t => t.Name == typeName))
            {
                var controllerType = typeof(GenericController<>)
                    .MakeGenericType(entityType.AsType())
                    .GetTypeInfo();

               //Normally I would expect there to be an overload to configure the controller name
               //feature.Controllers.Add(controllerType, entityConfig.ControllerName);
            }
        }
    }
}

然而我需要找到一种方法来覆盖控制器的路由。文档中唯一关于此的信息是如何创建控制器约定,像这样:

public class GenericControllerNameConvention : Attribute, IControllerModelConvention
{
    public void Apply(ControllerModel controller)
    {
        if (controller.ControllerType.GetGenericTypeDefinition() != 
        typeof(GenericController<>))
        {
            return;
        }

        var entityType = controller.ControllerType.GenericTypeArguments[0];
        controller.ControllerName = entityType.Name;
    }
}

这不起作用,因为它是在编译时完成的。我需要用户能够在启动时覆盖控制器名称,我该如何实现?


你能澄清一下启动时的意思吗?你的意思是可以根据需要添加实体,在应用程序重新启动时,控制器将自动出现(作为路由)。例如,EntityA有一个Get方法,那么路由将会动态生成为/entitya/get/ - Nico
@Nico,是的,我希望用户能够在路由中覆盖控制器名称,因此我注册了一个通用控制器'RestController<TDTO>',用户应该能够传递一个映射来手动覆盖名称,这样路由就会是Api/ControllerA/4,对应于get{id}。 - johnny 5
1个回答

6
根据您的评论和代码,您几乎已经达到了实现这一目标的水平。需要注意的是,我缩减了示例的内容,以便我能够设置测试。
比如,我有一个基本通用控制器,如下:
public class GenericController<T> : Controller
    where T: class
{

    public IActionResult Get()
    {
        return Content(typeof(T).FullName);
    }
}

我现在有一个带有Get操作的类型控制器。大部分代码都很正确。所以我的特性提供者是这样的(注意我有一个静态类型数组):

public class GenericControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature>
{
    public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
    {
        foreach (var entityConfig in ControllerEntity.EntityTypes)
        {
            var entityType = entityConfig;
            var typeName = entityType.Name + "Controller";
            if (!feature.Controllers.Any(t => t.Name == typeName))
            {
                var controllerType = typeof(GenericController<>)
                    .MakeGenericType(entityType)
                    .GetTypeInfo();

                feature.Controllers.Add(controllerType);
            }
        }
    }
}

接下来是 IControllerModelConvention 接口的实现。
public class GenericControllerModelConvention : IControllerModelConvention
{
    public void Apply(ControllerModel controller)
    {
        if (!controller.ControllerType.IsGenericType || controller.ControllerType.GetGenericTypeDefinition() != typeof(GenericController<>))
        {
            return;
        }

        var entityType = controller.ControllerType.GenericTypeArguments[0];
        controller.ControllerName = entityType.Name + "Controller";
        controller.RouteValues["Controller"] = entityType.Name;
    }
}

最后,启动项是所有魔法发生的地方。基本上我们将IControllerModelConvention注册到MVC约定选项中,然后注册FeatureProvider

public void ConfigureServices(IServiceCollection services)
{
    var mvcBuilder = services.AddMvc();
    mvcBuilder.AddMvcOptions(o => o.Conventions.Add(new GenericControllerModelConvention()));
    mvcBuilder.ConfigureApplicationPartManager(c =>
    {
        c.FeatureProviders.Add(new GenericControllerFeatureProvider());
    });
}

在我审查中,有两件事引起了我的注意。

  1. 我不确定为什么你将GenericControllerNameConvention设为属性?
  2. 您应该将Controller路由值隐式设置为实体类型(而不是类型+名称)。

给定两个实体(EntityA和EntityB),控制器的结果为

/Entitya/get/打印WebApplication11.Infrastructure.EntityA

/Entityb/get/打印WebApplication11.Infrastructure.EntityB


1
谢谢,我将我的约定作为属性,因为这是文档中唯一提到的内容。我在我的通用控制器上注册了一个默认的控制器名称约定,这样它就不会默认生成像GenericController`EntityA这样的名称了。 - johnny 5
1
我认为只在Convention中设置控制器名称就足够了,但关键在于隐式设置控制器的路由值,使路由中间件能够适当地处理请求。很高兴你成功了。 - Nico

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