gpt4 book ai didi

asp.net-web-api2 - 使用未绑定(bind)函数时具有多个路由的 OData 服务

转载 作者:行者123 更新时间:2023-12-04 21:05:03 25 4
gpt4 key购买 nike

有谁知道如何让 .NET 服务中托管的 OData v4 与多个路由一起工作?

我有以下内容:

config.MapODataServiceRoute("test1", "test1", GetEdmModelTest1());
config.MapODataServiceRoute("test2", "test2", GetEdmModelTest2());

每个 GetEdmModel 方法都有映射对象。
我可以按以下方式使用服务(这工作正常):

http://testing.com/test1/objects1()
http://testing.com/test2/objects2()

但是,如果我尝试调用如下函数(将不起作用):
[HttpGet]
[ODataRoute("test1/TestFunction1()")]
public int TestFunction1()
{ return 1; }

它会抛出以下错误:

The path template 'test1/TestFunction1()' on the action 'TestFunction1' in controller 'Testing' is not a valid OData path template. Resource not found for the segment 'test1'.



然而,如果我删除了“test2”的“MapODataServiceRoute”,那么只有一条路线,一切正常。

如何让它与多条路线一起使用?

** 我在下面发布了该问题的完整示例 **
https://github.com/OData/WebApi/issues/1223

** 我尝试了下面列出的 OData 版本示例,但存在以下问题 **
https://github.com/OData/ODataSamples/tree/master/WebApi/v4/ODataVersioningSample
我之前尝试过“OData 版本”示例,但没有成功。
似乎未绑定(bind)(未绑定(bind)是目标)不遵循相同的路由规则是正常的服务调用。

前任。如果您下载“OData 版本”示例并执行以下操作。
  • 在 V1 -> WebApiConfig.cs 添加builder.Function(nameof(Controller.ProductsV1Controller.Test)).Returns<string>();
  • 在 V2 -> WebApiConfig.cs 添加
    builder.Function(nameof(Controller.ProductsV2Controller.Test)).Returns<string>();
  • 在 V1 -> ProductsV1Controller.cs 添加[HttpGet]
    [ODataRoute("Test()")]
    public string Test()
    { return "V1_Test"; }
  • 在 V2 -> ProductsV2Controller.cs 添加[HttpGet]
    [ODataRoute("Test()")]
    public string Test()
    { return "V2_Test"; }

  • 现在用这个来称呼它。 “/versionbyroute/v1/Test() ”,你会得到“V2_Test”

    问题是“GetControllerName”在使用未绑定(bind)的函数/ Action 时不知道如何获取 Controller 。
    这就是为什么我发现的大多数示例代码在尝试“推断” Controller 时都失败了。

    最佳答案

    看看OData Versioning Sample为底漆。

    The key point of trouble is usually that the DefaultHttpControllerSelector maps controllers by local name, not fullname/namespace.



    如果您的实体类型和 Controller 名称在两个 EdmModel 中是唯一的,则您不必做任何特别的事情,它应该开箱即用。上面的示例通过强制您将字符串值注入(inject) Controller 类的物理名称以使它们唯一,然后在 ODataVersionControllerSelector 中利用了这个概念。 GetControllerName被覆盖以将传入路由映射到自定义 Controller 名称

    如果 Controller 的唯一名称似乎很难,并且您更愿意使用完整的命名空间(这意味着您的 Controller 名称逻辑仍然是标准的),那么您当然可以实现自己的逻辑来在覆盖 DefaultHttpControllerSelector 时选择特定的 Controller 类实例.只需覆盖 SelectController反而。此方法需要返回 HttpControllerDescriptor 的实例这比样本更复杂一些。

    为了向您展示我的意思,我将发布一个旧项目需求的解决方案,这与您的有点不同。我有一个管理对多个数据库的访问的 WebAPI 项目,这些数据库具有相似的架构,许多实体名称相同,这意味着这些 Controller 类将具有相同的名称。 Controller 由文件夹/命名空间构成,因此有一个名为 DB 的根文件夹,然后每个数据库都有一个文件夹,然后 Controller 就在那里。

    enter image description here

    You can see that this project has many different schemas, they effectively map to versions of an evolving solution, the non-DB namespaces in this image are a mix of OData v4, v3 and standard REST apis. It is possible to get all these beasts to co-exist ;)



    这种对 HttpControllerSelector 的覆盖会检查一次运行时以缓存所有 Controller 类的列表,然后通过将路由前缀匹配到正确的 Controller 类来映射传入的路由请求。
    /// <summary>
    /// Customised controller for intercepting traffic for the DB Odata feeds.
    /// Any route that is not prefixed with ~/DB/ will not be intercepted or processed via this controller
    /// <remarks>Will instead be directed to the base class</remarks>
    /// </summary>
    public class DBODataHttpControllerSelector : DefaultHttpControllerSelector
    {
    private readonly HttpConfiguration _configuration;

    public DBODataHttpControllerSelector(HttpConfiguration config)
    : base(config)
    {
    _configuration = config;
    }

    // From: http://www.codeproject.com/Articles/741326/Introduction-to-Web-API-Versioning
    private Dictionary<string, HttpControllerDescriptor> _controllerMap = null;
    private List<string> _duplicates = new List<string>();
    /// <summary>
    /// Because we are interested in supporting nested namespaces similar to MVC "Area"s we need to
    /// Index our available controller classes by the potential url segments that might be passed in
    /// </summary>
    /// <returns></returns>
    private Dictionary<string, HttpControllerDescriptor> InitializeControllerDictionary()
    {
    if(_controllerMap != null)
    return _controllerMap;

    _controllerMap = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);

    // Create a lookup table where key is "namespace.controller". The value of "namespace" is the last
    // segment of the full namespace. For example:
    // MyApplication.Controllers.V1.ProductsController => "V1.Products"
    IAssembliesResolver assembliesResolver = _configuration.Services.GetAssembliesResolver();
    IHttpControllerTypeResolver controllersResolver = _configuration.Services.GetHttpControllerTypeResolver();

    ICollection<Type> controllerTypes = controllersResolver.GetControllerTypes(assembliesResolver);

    foreach (Type t in controllerTypes)
    {
    var segments = t.Namespace.Split(Type.Delimiter);

    // For the dictionary key, strip "Controller" from the end of the type name.
    // This matches the behavior of DefaultHttpControllerSelector.
    var controllerName = t.Name.Remove(t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length);

    var key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}.{2}", segments[segments.Length - 2], segments[segments.Length - 1], controllerName);

    // Check for duplicate keys.
    if (_controllerMap.Keys.Contains(key))
    {
    _duplicates.Add(key);
    }
    else
    {
    _controllerMap[key] = new HttpControllerDescriptor(_configuration, t.Name, t);
    }
    }

    // Remove any duplicates from the dictionary, because these create ambiguous matches.
    // For example, "Foo.V1.ProductsController" and "Bar.V1.ProductsController" both map to "v1.products".
    // CS: Ahem... thats why I've opted to go 3 levels of depth to key name, but this still applies if the duplicates are there again
    foreach (string s in _duplicates)
    {
    _controllerMap.Remove(s);
    }
    return _controllerMap;
    }
    /// <summary>
    /// Because we are interested in supporting nested namespaces we want the full route
    /// to match to the full namespace (or at least the right part of it)
    /// </summary>
    /// <returns></returns>
    private Dictionary<string, HttpControllerDescriptor> _fullControllerMap = null;
    private Dictionary<string, HttpControllerDescriptor> InitializeFullControllerDictionary()
    {
    if(_fullControllerMap != null)
    return _fullControllerMap;

    _fullControllerMap = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);

    // Create a lookup table where key is "namespace.controller". The value of "namespace" is the last
    // segment of the full namespace. For example:
    // MyApplication.Controllers.V1.ProductsController => "V1.Products"
    IAssembliesResolver assembliesResolver = _configuration.Services.GetAssembliesResolver();
    IHttpControllerTypeResolver controllersResolver = _configuration.Services.GetHttpControllerTypeResolver();

    ICollection<Type> controllerTypes = controllersResolver.GetControllerTypes(assembliesResolver);

    foreach (Type t in controllerTypes)
    {
    var segments = t.Namespace.Split(Type.Delimiter);

    // For the dictionary key, strip "Controller" from the end of the type name.
    // This matches the behavior of DefaultHttpControllerSelector.
    var controllerName = t.Name.Remove(t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length);

    var key = t.FullName;// t.Namespace + "." + controllerName;
    _fullControllerMap[key] = new HttpControllerDescriptor(_configuration, t.Name, t);
    }

    return _fullControllerMap;
    }

    /// <summary>
    /// Select the controllers with a simulated MVC area sort of functionality, but only for the ~/DB/ route
    /// </summary>
    /// <param name="request"></param>
    /// <returns></returns>
    public override System.Web.Http.Controllers.HttpControllerDescriptor SelectController(System.Net.Http.HttpRequestMessage request)
    {
    string rootPath = "db";
    IHttpRouteData routeData = request.GetRouteData();
    string[] uriSegments = request.RequestUri.LocalPath.Split('/');
    if (uriSegments.First().ToLower() == rootPath || uriSegments[1].ToLower() == rootPath)
    {
    #region DB Route Selector
    // If we can find a known api and a controller, then redirect to the correct controller
    // Otherwise allow the standard select to work
    string[] knownApis = new string[] { "tms", "srg", "cumulus" };


    // Get variables from the route data.
    /* support version like this:
    * config.Routes.MapODataRoute(
    routeName: "ODataDefault",
    routePrefix: "{version}/{area}/{controller}",
    model: model);
    object versionName = null;
    routeData.Values.TryGetValue("version", out versionName);

    object apiName = null;
    routeData.Values.TryGetValue("api", out apiName);

    object controllerName = null;
    routeData.Values.TryGetValue("controller", out controllerName);
    * */

    // CS: we'll just use the local path AFTER the root path
    // db/tms/contact
    // db/srg/contact
    // Implicity parse this as
    // db/{api}/{controller}
    // so [0] = ""
    // so [1] = "api"
    // so [2] = "version" (optional)
    // so [2 or 3] = "controller"

    if (uriSegments.Length > 3)
    {
    string apiName = uriSegments[2];
    if (knownApis.Contains(string.Format("{0}", apiName).ToLower()))
    {
    string version = "";
    string controllerName = uriSegments[3];
    if (controllerName.ToLower().StartsWith("v")
    // and the rest of the name is numeric
    && !controllerName.Skip(1).Any(c => !Char.IsNumber(c))
    )
    {
    version = controllerName;
    controllerName = uriSegments[4];
    }

    // if the route has an OData item selector (#) then this needs to be trimmed from the end.
    if (controllerName.Contains('('))
    controllerName = controllerName.Substring(0, controllerName.IndexOf('('));

    string fullName = string.Format(CultureInfo.InvariantCulture, "{0}.{1}.{2}", apiName, version, controllerName).Replace("..", ".");


    // Search for the controller.
    // _controllerTypes is a list of HttpControllerDescriptors
    var descriptors = InitializeControllerDictionary().Where(t => t.Key.EndsWith(fullName, StringComparison.OrdinalIgnoreCase)).ToList();
    if (descriptors.Any())
    {
    var descriptor = descriptors.First().Value;
    if (descriptors.Count > 1)
    {
    descriptor = null;
    // Assume that the version was missing, and we have implemented versioning for that controller
    // If there is a row with no versioning, so no v1, v2... then use that
    // if all rows are versioned, use the highest version
    if (descriptors.Count(d => d.Key.Split('.').Length == 2) == 1)
    descriptor = descriptors.First(d => d.Key.Split('.').Length == 2).Value;
    else if (descriptors.Count(d => d.Key.Split('.').Length > 2) == descriptors.Count())
    descriptor = descriptors
    .Where(d => d.Key.Split('.').Length > 2)
    .OrderByDescending(d => d.Key.Split('.')[1])
    .First().Value;
    if (descriptor == null)
    throw new HttpResponseException(
    request.CreateErrorResponse(HttpStatusCode.InternalServerError,
    "Multiple controllers were found that match this un-versioned request."));
    }
    if (descriptor != null)
    return descriptor;
    }

    if (_duplicates.Any(d => d.ToLower() == fullName.ToLower()))
    throw new HttpResponseException(
    request.CreateErrorResponse(HttpStatusCode.InternalServerError,
    "Multiple controllers were found that match this request."));
    }
    }
    #endregion DB Route Selector
    }
    else
    {
    // match on class names that match the route.
    // So if the route is odata.tms.testController
    // Then the class name must also match
    // Add in an option to doing a string mapping, so that
    // route otms can mapp to odata.tms

    // TODO: add any other custom logic for selecting the controller that you want, alternatively try this style syntax in your route config:
    //routes.MapRoute(
    // name: "Default",
    // url: "{controller}/{action}/{id}",
    // defaults: new { controller = "Home", action = "RegisterNow", id = UrlParameter.Optional },
    // namespaces: new[] { "YourCompany.Controllers" }
    //);

    // Because controller path mapping might be controller/navigationproperty/action
    // We need to check for the following matches:
    // controller.navigationproperty.actionController
    // controller.navigationpropertyController
    // controllerController

    string searchPath = string.Join(".", uriSegments).ToLower().Split('(')[0] + "controller";
    var descriptors = InitializeFullControllerDictionary().Where(t => t.Key.ToLower().Contains(searchPath)).ToList();
    if (descriptors.Any())
    {
    var descriptor = descriptors.First().Value;
    if (descriptors.Count > 1)
    {
    descriptor = null;
    // In this mode, I think we should only ever have a single match, ready to prove me wrong?
    if (descriptor == null)
    throw new HttpResponseException(
    request.CreateErrorResponse(HttpStatusCode.InternalServerError,
    "Multiple controllers were found that match this namespace request."));
    }
    if (descriptor != null)
    return descriptor;
    }

    }
    return base.SelectController(request);
    }

    }

    关于asp.net-web-api2 - 使用未绑定(bind)函数时具有多个路由的 OData 服务,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48454874/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com