【问题标题】:Testing route configuration in ASP.NET WebApi在 ASP.NET WebApi 中测试路由配置
【发布时间】:2018-06-30 03:40:01
【问题描述】:

我正在尝试对我的WebApi 路由配置进行一些单元测试。我想测试路由"/api/super" 是否映射到我的SuperControllerGet() 方法。我已经设置了以下测试,但遇到了一些问题。

public void GetTest()
{
    var url = "~/api/super";

    var routeCollection = new HttpRouteCollection();
    routeCollection.MapHttpRoute("DefaultApi", "api/{controller}/");

    var httpConfig = new HttpConfiguration(routeCollection);
    var request = new HttpRequestMessage(HttpMethod.Get, url);

    // exception when url = "/api/super"
    // can get around w/ setting url = "http://localhost/api/super"
    var routeData = httpConfig.Routes.GetRouteData(request);
    request.Properties[HttpPropertyKeys.HttpRouteDataKey] = routeData;

    var controllerSelector = new DefaultHttpControllerSelector(httpConfig);

    var controlleDescriptor = controllerSelector.SelectController(request);

    var controllerContext =
        new HttpControllerContext(httpConfig, routeData, request);
    controllerContext.ControllerDescriptor = controlleDescriptor;

    var selector = new ApiControllerActionSelector();
    var actionDescriptor = selector.SelectAction(controllerContext);

    Assert.AreEqual(typeof(SuperController),
        controlleDescriptor.ControllerType);
    Assert.IsTrue(actionDescriptor.ActionName == "Get");
}

我的第一个问题是,如果我没有指定完全限定的 URL,httpConfig.Routes.GetRouteData(request); 会引发 InvalidOperationException 异常,并显示“相对 URI 不支持此操作”的消息。

我的存根配置显然遗漏了一些东西。我更喜欢使用相对 URI,因为使用完全限定的 URI 进行路由测试似乎不合理。

我上面配置的第二个问题是我没有按照 RouteConfig 中的配置测试我的路由,而是使用:

var routeCollection = new HttpRouteCollection();
routeCollection.MapHttpRoute("DefaultApi", "api/{controller}/");

如何使用在典型 Global.asax 中配置的分配的RouteTable.Routes

public class MvcApplication : HttpApplication
{
    protected void Application_Start()
    {
        // other startup stuff

        RouteConfig.RegisterRoutes(RouteTable.Routes);
    }
}

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        // route configuration
    }
}

我上面提到的进一步内容可能不是最好的测试配置。如果有更精简的方法,我会全力以赴。

【问题讨论】:

    标签: unit-testing routes asp.net-web-api


    【解决方案1】:

    我最近在测试我的 Web API 路由,我是这样做的。

    1. 首先,我创建了一个助手来将所有 Web API 路由逻辑移到那里:
        public static class WebApi
        {
            public static RouteInfo RouteRequest(HttpConfiguration config, HttpRequestMessage request)
            {
                // create context
                var controllerContext = new HttpControllerContext(config, Substitute.For<IHttpRouteData>(), request);
    
                // get route data
                var routeData = config.Routes.GetRouteData(request);
                RemoveOptionalRoutingParameters(routeData.Values);
    
                request.Properties[HttpPropertyKeys.HttpRouteDataKey] = routeData;
                controllerContext.RouteData = routeData;
    
                // get controller type
                var controllerDescriptor = new DefaultHttpControllerSelector(config).SelectController(request);
                controllerContext.ControllerDescriptor = controllerDescriptor;
    
                // get action name
                var actionMapping = new ApiControllerActionSelector().SelectAction(controllerContext);
    
                return new RouteInfo
                {
                    Controller = controllerDescriptor.ControllerType,
                    Action = actionMapping.ActionName
                };
            }
    
            private static void RemoveOptionalRoutingParameters(IDictionary<string, object> routeValues)
            {
                var optionalParams = routeValues
                    .Where(x => x.Value == RouteParameter.Optional)
                    .Select(x => x.Key)
                    .ToList();
    
                foreach (var key in optionalParams)
                {
                    routeValues.Remove(key);
                }
            }
        }
    
        public class RouteInfo
        {
            public Type Controller { get; set; }
    
            public string Action { get; set; }
        }
    
    1. 假设我有一个单独的类来注册 Web API 路由(它默认在 Visual Studio ASP.NET MVC 4 Web 应用程序项目中创建,在 App_Start 文件夹中):
        public static class WebApiConfig
        {
            public static void Register(HttpConfiguration config)
            {
                config.Routes.MapHttpRoute(
                    name: "DefaultApi",
                    routeTemplate: "api/{controller}/{id}",
                    defaults: new { id = RouteParameter.Optional }
                );
            }
        }
    
    1. 我可以轻松测试我的路线:
        [Test]
        public void GET_api_products_by_id_Should_route_to_ProductsController_Get_method()
        {
            // setups
            var request = new HttpRequestMessage(HttpMethod.Get, "http://myshop.com/api/products/1");
            var config = new HttpConfiguration();
    
            // act
            WebApiConfig.Register(config);
            var route = WebApi.RouteRequest(config, request);
    
            // asserts
            route.Controller.Should().Be<ProductsController>();
            route.Action.Should().Be("Get");
        }
    
        [Test]
        public void GET_api_products_Should_route_to_ProductsController_GetAll_method()
        {
            // setups
            var request = new HttpRequestMessage(HttpMethod.Get, "http://myshop.com/api/products");
            var config = new HttpConfiguration();
    
            // act
            WebApiConfig.Register(config);
            var route = WebApi.RouteRequest(config, request);
    
            // asserts
            route.Controller.Should().Be<ProductsController>();
            route.Action.Should().Be("GetAll");
        }
    
        ....
    

    以下几点说明:

    • 是的,我使用的是绝对 URL。但我在这里没有看到任何问题,因为这些是虚假 URL,我不需要配置任何东西就可以让它们工作,它们代表对我们 Web 服务的真实请求。
    • 如果它们是在具有 HttpConfiguration 依赖项的单独类中配置的(如上例中),则无需将路由映射代码复制到测试中。
    • 我在上面的示例中使用了 NUnit、NSubstitute 和 FluentAssertions,当然,使用任何其他测试框架也很容易做到这一点。

    【讨论】:

    • 我不喜欢绝对 URI 的需要,但它肯定是我最不关心的问题。现在我考虑将这两种配置分开实际上是更可取的,因为它更具可测试性,并且在启动时确实增加了最小的开销。除了更改为不同的模拟和断言机制所需的调整之外,这非常有效。感谢您的精彩回答!
    • 我只是在请求中使用HttpMethod.Post 并验证route.Action.Should().Be("Post")。我没有在消息正文中传递任何数据,因为它与路由无关。您还可以返回并测试routeData 对象(来自 Web API 帮助程序)以检查操作路由参数。
    • routeData 将为空,因为 config.Routes.GetRouteData(request) 将为空。当您检查不正确的路线时,这会导致异常。我们不应该也有 if(routeData == null) throw new HttpException(404,"找不到路由");并断言异常..只需添加我今天经历的一点点..
    • 不错!不过,我同意@ashutoshrina 关于空值检查的观点。是否有理由通过 RemoveOptionalRoutingParameter 清除 routeData.Values?它们对于断言传入的参数的值很有用。
    • 这种方法是否适用于属性路由?它似乎对内部细节做了一些假设。我认为运行更多的管道(如 Yishai 的方法)会不那么脆弱。
    【解决方案2】:

    ASP.NET Web API 2 的迟到答案(我只测试了那个版本)。我使用了来自 Nuget 的 MvcRouteTester.Mvc5,它为我完成了这项工作。你可以写如下。

    [TestClass]
    public class RouteTests
    {
        private HttpConfiguration config;
        [TestInitialize]
        public void MakeRouteTable()
        {
            config = new HttpConfiguration();
            WebApiConfig.Register(config);
            config.EnsureInitialized();
        }
        [TestMethod]
        public void GetTest()
        {
            config.ShouldMap("/api/super")
                .To<superController>(HttpMethod.Get, x => x.Get());
        }
    }
    

    我必须将 nuget 包 Microsoft Asp.Net MVC 版本 5.0.0 添加到测试项目中。这不太漂亮,但我没有找到更好的解决方案,这对我来说是可以接受的。您可以在 nuget 包管理器控制台中像这样安装旧版本:

    Get-Project Tests | install-package microsoft.aspnet.mvc -version 5.0.0

    它也适用于 System.Web.Http.RouteAttribute。

    【讨论】:

      【解决方案3】:

      此答案适用于 WebAPI 2.0 及更高版本

      阅读Whyleee 的回答后,我注意到该方法基于耦合且脆弱的假设:

      1. 该方法尝试重新创建操作选择,并假定 Web API 中的内部实现细节。
      2. 当有一个众所周知的公共可扩展点允许替换它时,它假定正在使用默认控制器选择器。

      另一种方法是使用轻量级功能测试。 这种方法的步骤是:

      1. 使用您的 WebApiConfig.Register 方法初始化测试 HttpConfiguration 对象,模仿在现实世界中初始化应用程序的方式。
      2. 将自定义身份验证过滤器添加到捕获该级别操作信息的测试配置对象。这可以通过开关直接在产品代码中注入或完成。 2.1 身份验证过滤器将短路任何过滤器以及操作代码,因此无需担心在操作方法本身中运行的实际代码。
      3. 使用内存服务器 (HttpServer),并发出请求。这种方法使用内存中的通道,因此不会影响网络。
      4. 将捕获的操作信息与预期信息进行比较。
      [TestClass]
      public class ValuesControllerTest
      {
          [TestMethod]
          public void ActionSelection()
          {
              var config = new HttpConfiguration();
              WebApiConfig.Register(config);
      
              Assert.IsTrue(ActionSelectorValidator.IsActionSelected(
                  HttpMethod.Post,
                  "http://localhost/api/values/",
                  config,
                  typeof(ValuesController),
                  "Post"));
          }
       }
      

      这个助手执行管道,并验证由 身份验证过滤器,也可以捕获其他属性或 通过在初始化时将 lambda 传递给过滤器,可以实现一个客户过滤器,该过滤器在每个测试中直接进行验证。

       public class ActionSelectorValidator
       {
          public static bool IsActionSelected(
              HttpMethod method,
              string uri,
              HttpConfiguration config,
              Type controller,
              string actionName)
          {
              config.Filters.Add(new SelectedActionFilter());
              var server = new HttpServer(config);
              var client = new HttpClient(server);
              var request = new HttpRequestMessage(method, uri);
              var response = client.SendAsync(request).Result;
              var actionDescriptor = (HttpActionDescriptor)response.RequestMessage.Properties["selected_action"];
      
              return controller == actionDescriptor.ControllerDescriptor.ControllerType && actionName == actionDescriptor.ActionName;
          }
      }
      

      此过滤器运行并阻止过滤器或操作代码的所有其他执行。

      public class SelectedActionFilter : IAuthenticationFilter
      {
          public Task AuthenticateAsync(
               HttpAuthenticationContext context,
               CancellationToken cancellationToken)
          {
              context.ErrorResult = CreateResult(context.ActionContext);
      
             // short circuit the rest of the authentication filters
              return Task.FromResult(0);
          }
      
          public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
          {
              var actionContext = context.ActionContext;
      
              actionContext.Request.Properties["selected_action"] = 
                  actionContext.ActionDescriptor;
              context.Result = CreateResult(actionContext); 
      
      
              return Task.FromResult(0);
          }
      
          private static IHttpActionResult CreateResult(
              HttpActionContext actionContext)
          {
              var response = new HttpResponseMessage()
                  { RequestMessage = actionContext.Request };
      
              actionContext.Response = response;
      
              return new ByPassActionResult(response);
          }
      
          public bool AllowMultiple { get { return true; } }
      }
      

      会导致执行短路的结果

      internal class ByPassActionResult : IHttpActionResult
      {
          public HttpResponseMessage Message { get; set; }
      
          public ByPassActionResult(HttpResponseMessage message)
          {
              Message = message;
          }
      
          public Task<HttpResponseMessage> 
             ExecuteAsync(CancellationToken cancellationToken)
          {
             return Task.FromResult<HttpResponseMessage>(Message);
          }
      }
      

      【讨论】:

      • 我已经尝试过了,它运行良好,直到我在操作上添加了授权属性。您如何对授权操作进行单元测试?
      • 这实际上是一个好点!所以另一种方法(感谢 David Matson 的帮助)是使用身份验证过滤器而不是操作过滤器,它只会失败(因此会短路)。只要确保当您在过滤器列表的顶部和底部添加过滤器时,如果有多个身份验证过滤器可用。我已经更新了上面的答案
      • 不是这个答案应该包括这个,但我遇到了其他问题,确保在这种技术对我有用之前正确配置了 Autofac。只是提醒那些使用上述简单示例之外的东西的人。一旦我弄清楚了,尽管这很好用,并处理了查询参数的情况……其他答案没有解决。
      • 假设 1 是正确的。这里的许多其他解决方案都是垃圾,因为它们实际上除了测试代码之外没有测试任何东西! +1 给你。
      • 对不起,你死了……Assert.IsNull(context.Result);ChallengeAsync 中做了什么?它对我来说失败了“消息:预期:null 但是是:”而且我看不出它不会为空?
      【解决方案4】:

      我采用了 Keith Jackson 的解决方案并将其修改为:

      a) 使用 asp.net web api 2 - 属性路由以及作为老派路由

          和

      b) 不仅要验证路由参数名称,还要验证它们的值

      例如对于以下路线

          [HttpPost]
          [Route("login")]
          public HttpResponseMessage Login(string username, string password)
          {
              ...
          }
      
      
          [HttpPost]
          [Route("login/{username}/{password}")]
          public HttpResponseMessage LoginWithDetails(string username, string password)
          {
              ...
          }
      

      您可以验证路由匹配正确的 http 方法、控制器、操作和参数:

          [TestMethod]
          public void Verify_Routing_Rules()
          {
              "http://api.appname.com/account/login"
                 .ShouldMapTo<AccountController>("Login", HttpMethod.Post);
      
              "http://api.appname.com/account/login/ben/password"
                  .ShouldMapTo<AccountController>(
                     "LoginWithDetails", 
                     HttpMethod.Post, 
                     new Dictionary<string, object> { 
                         { "username", "ben" }, { "password", "password" } 
                     });
          }
      

      Keith Jackson 对Whyleee 解决方案的修改。

          public static class RoutingTestHelper
          {
              /// <summary>
              ///     Routes the request.
              /// </summary>
              /// <param name="config">The config.</param>
              /// <param name="request">The request.</param>
              /// <returns>Inbformation about the route.</returns>
              public static RouteInfo RouteRequest(HttpConfiguration config, HttpRequestMessage request)
              {
                  // create context
                  var controllerContext = new HttpControllerContext(config, new Mock<IHttpRouteData>().Object, request);
      
                  // get route data
                  var routeData = config.Routes.GetRouteData(request);
                  RemoveOptionalRoutingParameters(routeData.Values);
      
                  HttpActionDescriptor actionDescriptor = null;
                  HttpControllerDescriptor controllerDescriptor = null;
      
                  // Handle web api 2 attribute routes
                  if (routeData.Values.ContainsKey("MS_SubRoutes"))
                  {
                      var subroutes = (IEnumerable<IHttpRouteData>)routeData.Values["MS_SubRoutes"];
                      routeData = subroutes.First();
                      actionDescriptor = ((HttpActionDescriptor[])routeData.Route.DataTokens.First(token => token.Key == "actions").Value).First();
                      controllerDescriptor = actionDescriptor.ControllerDescriptor;
                  }
                  else
                  {
                      request.Properties[HttpPropertyKeys.HttpRouteDataKey] = routeData;
                      controllerContext.RouteData = routeData;
      
                      // get controller type
                      controllerDescriptor = new DefaultHttpControllerSelector(config).SelectController(request);
                      controllerContext.ControllerDescriptor = controllerDescriptor;
      
                      // get action name
                      actionDescriptor = new ApiControllerActionSelector().SelectAction(controllerContext);
      
                  }
      
                  return new RouteInfo
                  {
                      Controller = controllerDescriptor.ControllerType,
                      Action = actionDescriptor.ActionName,
                      RouteData = routeData
                  };
              }
      
      
              #region | Extensions |
      
              public static bool ShouldMapTo<TController>(this string fullDummyUrl, string action, Dictionary<string, object> parameters = null)
              {
                  return ShouldMapTo<TController>(fullDummyUrl, action, HttpMethod.Get, parameters);
              }
      
              public static bool ShouldMapTo<TController>(this string fullDummyUrl, string action, HttpMethod httpMethod, Dictionary<string, object> parameters = null)
              {
                  var request = new HttpRequestMessage(httpMethod, fullDummyUrl);
                  var config = new HttpConfiguration();
                  WebApiConfig.Register(config);
                  config.EnsureInitialized();
      
                  var route = RouteRequest(config, request);
      
                  var controllerName = typeof(TController).Name;
                  if (route.Controller.Name != controllerName)
                      throw new Exception(String.Format("The specified route '{0}' does not match the expected controller '{1}'", fullDummyUrl, controllerName));
      
                  if (route.Action.ToLowerInvariant() != action.ToLowerInvariant())
                      throw new Exception(String.Format("The specified route '{0}' does not match the expected action '{1}'", fullDummyUrl, action));
      
                  if (parameters != null && parameters.Any())
                  {
                      foreach (var param in parameters)
                      {
                          if (route.RouteData.Values.All(kvp => kvp.Key != param.Key))
                              throw new Exception(String.Format("The specified route '{0}' does not contain the expected parameter '{1}'", fullDummyUrl, param));
      
                          if (!route.RouteData.Values[param.Key].Equals(param.Value))
                              throw new Exception(String.Format("The specified route '{0}' with parameter '{1}' and value '{2}' does not equal does not match supplied value of '{3}'", fullDummyUrl, param.Key, route.RouteData.Values[param.Key], param.Value));
                      }
                  }
      
                  return true;
              }
      
              #endregion
      
      
              #region | Private Methods |
      
              /// <summary>
              ///     Removes the optional routing parameters.
              /// </summary>
              /// <param name="routeValues">The route values.</param>
              private static void RemoveOptionalRoutingParameters(IDictionary<string, object> routeValues)
              {
                  var optionalParams = routeValues
                      .Where(x => x.Value == RouteParameter.Optional)
                      .Select(x => x.Key)
                      .ToList();
      
                  foreach (var key in optionalParams)
                  {
                      routeValues.Remove(key);
                  }
              }
      
              #endregion
          }
      
          /// <summary>
          ///     Route information
          /// </summary>
          public class RouteInfo
          {
              public Type Controller { get; set; }
              public string Action { get; set; }
              public IHttpRouteData RouteData { get; set; }
          }
      

      【讨论】:

      • 很好,但它确实无法区分指向同一路由的 POST 和 GET。例如。 api/patient (GET) api/patient (POST) POST 需要一个对象。
      【解决方案5】:

      感谢whyleee上面的回答!

      我已将它与 WebApiContrib.Testing 库中的一些我喜欢的语法元素组合在一起,这对我来说无法生成以下帮助程序类。

      这让我可以编写像这样真正轻量级的测试......

      [Test]
      [Category("Auth Api Tests")]
      public void TheAuthControllerAcceptsASingleItemGetRouteWithAHashString()
      {
          "http://api.siansplan.com/auth/sjkfhiuehfkshjksdfh".ShouldMapTo<AuthController>("Get", "hash");
      }
      
      [Test]
      [Category("Auth Api Tests")]
      public void TheAuthControllerAcceptsAPost()
      {
          "http://api.siansplan.com/auth".ShouldMapTo<AuthController>("Post", HttpMethod.Post);
      }
      

      我还稍微增强了它以允许在需要时测试参数(这是一个 params 数组,因此您可以添加所有您喜欢的内容,它只是检查它们是否存在)。这也适用于最小起订量,纯粹是因为它是我选择的框架......

      using Moq;
      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Net.Http;
      using System.Web.Http;
      using System.Web.Http.Controllers;
      using System.Web.Http.Dispatcher;
      using System.Web.Http.Hosting;
      using System.Web.Http.Routing;
      
      namespace SiansPlan.Api.Tests.Helpers
      {
          public static class RoutingTestHelper
          {
              /// <summary>
              /// Routes the request.
              /// </summary>
              /// <param name="config">The config.</param>
              /// <param name="request">The request.</param>
              /// <returns>Inbformation about the route.</returns>
              public static RouteInfo RouteRequest(HttpConfiguration config, HttpRequestMessage request)
              {
                  // create context
                  var controllerContext = new HttpControllerContext(config, new Mock<IHttpRouteData>().Object, request);
      
                  // get route data
                  var routeData = config.Routes.GetRouteData(request);
                  RemoveOptionalRoutingParameters(routeData.Values);
      
                  request.Properties[HttpPropertyKeys.HttpRouteDataKey] = routeData;
                  controllerContext.RouteData = routeData;
      
                  // get controller type
                  var controllerDescriptor = new DefaultHttpControllerSelector(config).SelectController(request);
                  controllerContext.ControllerDescriptor = controllerDescriptor;
      
                  // get action name
                  var actionMapping = new ApiControllerActionSelector().SelectAction(controllerContext);
      
                  var info = new RouteInfo(controllerDescriptor.ControllerType, actionMapping.ActionName);
      
                  foreach (var param in actionMapping.GetParameters())
                  {
                      info.Parameters.Add(param.ParameterName);
                  }
      
                  return info;
              }
      
              #region | Extensions |
      
              /// <summary>
              /// Determines that a URL maps to a specified controller.
              /// </summary>
              /// <typeparam name="TController">The type of the controller.</typeparam>
              /// <param name="fullDummyUrl">The full dummy URL.</param>
              /// <param name="action">The action.</param>
              /// <param name="parameterNames">The parameter names.</param>
              /// <returns></returns>
              public static bool ShouldMapTo<TController>(this string fullDummyUrl, string action, params string[] parameterNames)
              {
                  return ShouldMapTo<TController>(fullDummyUrl, action, HttpMethod.Get, parameterNames);
              }
      
              /// <summary>
              /// Determines that a URL maps to a specified controller.
              /// </summary>
              /// <typeparam name="TController">The type of the controller.</typeparam>
              /// <param name="fullDummyUrl">The full dummy URL.</param>
              /// <param name="action">The action.</param>
              /// <param name="httpMethod">The HTTP method.</param>
              /// <param name="parameterNames">The parameter names.</param>
              /// <returns></returns>
              /// <exception cref="System.Exception"></exception>
              public static bool ShouldMapTo<TController>(this string fullDummyUrl, string action, HttpMethod httpMethod, params string[] parameterNames)
              {
                  var request = new HttpRequestMessage(httpMethod, fullDummyUrl);
                  var config = new HttpConfiguration();
                  WebApiConfig.Register(config);
      
                  var route = RouteRequest(config, request);
      
                  var controllerName = typeof(TController).Name;
                  if (route.Controller.Name != controllerName)
                      throw new Exception(String.Format("The specified route '{0}' does not match the expected controller '{1}'", fullDummyUrl, controllerName));
      
                  if (route.Action.ToLowerInvariant() != action.ToLowerInvariant())
                      throw new Exception(String.Format("The specified route '{0}' does not match the expected action '{1}'", fullDummyUrl, action));
      
                  if (parameterNames.Any())
                  {
                      if (route.Parameters.Count != parameterNames.Count())
                          throw new Exception(
                              String.Format(
                                  "The specified route '{0}' does not have the expected number of parameters - expected '{1}' but was '{2}'",
                                  fullDummyUrl, parameterNames.Count(), route.Parameters.Count));
      
                      foreach (var param in parameterNames)
                      {
                          if (!route.Parameters.Contains(param))
                              throw new Exception(
                                  String.Format("The specified route '{0}' does not contain the expected parameter '{1}'",
                                                fullDummyUrl, param));
                      }
                  }
      
                  return true;
              }
      
              #endregion
      
              #region | Private Methods |
      
              /// <summary>
              /// Removes the optional routing parameters.
              /// </summary>
              /// <param name="routeValues">The route values.</param>
              private static void RemoveOptionalRoutingParameters(IDictionary<string, object> routeValues)
              {
                  var optionalParams = routeValues
                      .Where(x => x.Value == RouteParameter.Optional)
                      .Select(x => x.Key)
                      .ToList();
      
                  foreach (var key in optionalParams)
                  {
                      routeValues.Remove(key);
                  }
              }
      
              #endregion
          }
      
          /// <summary>
          /// Route information
          /// </summary>
          public class RouteInfo
          {
              #region | Construction |
      
              /// <summary>
              /// Initializes a new instance of the <see cref="RouteInfo"/> class.
              /// </summary>
              /// <param name="controller">The controller.</param>
              /// <param name="action">The action.</param>
              public RouteInfo(Type controller, string action)
              {
                  Controller = controller;
                  Action = action;
                  Parameters = new List<string>();
              }
      
              #endregion
      
              public Type Controller { get; private set; }
              public string Action { get; private set; }
              public List<string> Parameters { get; private set; }
          }
      }
      

      【讨论】:

        【解决方案6】:

        由于一些我无法弄清楚的细节,所有其他答案对我来说都失败了。

        这是一个使用GetRouteData()的完整示例:https://github.com/JayBazuzi/ASP.NET-WebApi-GetRouteData-example,创建如下:

        1. 在 VS 2013 中,新建项目 -> Web,ASP.NET Web 应用程序
        2. 选择 WebAPI。选中“添加单元测试”。
        3. 添加以下单元测试:

          [TestMethod]
          public void RouteToGetUser()
          {
              var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost:4567/api/users/me");
          
              var config = new HttpConfiguration();
              WebApiConfig.Register(config);
              config.EnsureInitialized();
          
              var result = config.Routes.GetRouteData(request);
          
              Assert.AreEqual("api/{controller}/{id}", result.Route.RouteTemplate);
          }
          

        【讨论】:

          【解决方案7】:

          要从路由集合中获取路由数据,在这种情况下,您确实需要提供完整的 URI(只需使用“http://localhost/api/super”)。

          要测试来自 RouteTable.Routes 的路由,您可以执行以下操作:

          var httpConfig = GlobalConfiguration.Configuration;
          httpConfig.Routes.MapHttpRoute("DefaultApi", "api/{controller}/");
          

          幕后发生的事情是 GlobalConfiguration 将使 RouteTable.Routes 适应 httpConfig.Routes。因此,当您将路由添加到 httpConfig.Routes 时,它实际上会添加到 RouteTable.Routes。但要使其工作,您需要托管在 ASP.NET 中,以便填充诸如 HostingEnvironment.ApplicationVirtualPath 之类的环境设置。

          【讨论】:

          • 是否有不需要完全限定 URI 的路由集合的替代方案?附加 localhost 并不可怕,只是有点烦人。 :) 这是用于单元测试的,因此在 ASP.NET 环境中托管是不可能的。知道除了ApplicationVirtualPath 之外还需要在HostingEnvironment 中存根以使RouteTable.Routes 正常运行吗?
          猜你喜欢
          • 2017-08-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-11-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多