简体   繁体   English

WebApi OData v3 OperationDescriptor根据格式(atom vs json)返回不同的Title / Target URI

[英]WebApi OData v3 OperationDescriptor returning different Title/Target URI depending on the format (atom vs json)

Consider the following simple ASP.NET Web Api with OData v3. 请考虑以下使用OData v3的简单ASP.NET Web Api。

MyEntity.cs MyEntity.cs

public class MyEntity
{
    public Guid Id { get; set; }
    public string Name { get; set; }
}

MyEntitiesController.cs MyEntitiesController.cs

public class MyEntitiesController : ODataController
{
    public IEnumerable<MyEntity> Get()
    {
        return new MyEntity[] { new MyEntity() { Id = Guid.NewGuid(), Name = "Name" } };
    }

    [HttpPost]
    public string MyAction()
    {
        return "Hello World!";
    }
}

WebApiConfig.cs WebApiConfig.cs

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        var modelBuilder = new ODataConventionModelBuilder();
        modelBuilder.Namespace = "MyNamespace";
        modelBuilder.ContainerName = "MyContainer";
        modelBuilder.EntitySet<MyEntity>("MyEntities");

        var action = modelBuilder.Entity<MyEntity>().Action("MyAction");
        action.Returns<string>();

        foreach (var structuralType in modelBuilder.StructuralTypes)
        {
            // Resets the namespace so that the service contains only 1 namespace.
            structuralType.GetType().GetProperty("Namespace").SetValue(structuralType, "MyNamespace");
        }

        var model = modelBuilder.GetEdmModel();
        config.Routes.MapODataServiceRoute("OData", "odata", model);
    }
}

On the client side, I added a simple Service Reference. 在客户端,我添加了一个简单的服务参考。

Program.cs Program.cs中

class Program
{
    static void Main(string[] args)
    {
        var contextAtom = new MyContainer(new Uri("http://localhost:63939/odata/"));
        contextAtom.Format.UseAtom();
        var myEntityAtom = contextAtom.MyEntities.First();

        // Outputs: http://localhost:63939/odata/MyEntities(guid'2c2431cd-4afa-422b-805b-8398b9a29fec')/MyAction
        var uriAtom = contextAtom.GetEntityDescriptor(myEntityAtom).OperationDescriptors.First().Target;
        Console.WriteLine(uriAtom);

        // Works fine using ATOM format!
        var responseAtom = contextAtom.Execute<string>(uriAtom, "POST", true);

        var contextJson = new MyContainer(new Uri("http://localhost:63939/odata/"));
        contextJson.Format.UseJson();
        var myEntityJson = contextJson.MyEntities.First();

        // Outputs: http://localhost:63939/odata/MyEntities(guid'f31a8332-025b-4dc9-9bd1-27437ae7966a')/MyContainer.MyAction
        var uriJson = contextJson.GetEntityDescriptor(myEntityJson).OperationDescriptors.First().Target;
        Console.WriteLine(uriJson);

        // Throws an exception using the JSON uri in JSON format!
        var responseJson = contextJson.Execute<string>(uriJson, "POST", true);

        // Works fine using ATOM uri in JSON format!
        var responseJson2 = contextJson.Execute<string>(uriAtom, "POST", true);
    }
}

My issue is that depending on the format used to query the entity, the operation descriptor target URI is different. 我的问题是,根据用于查询实体的格式,操作描述符目标URI是不同的。 The target URI coming from ATOM works fine, but the one coming from JSON always throws an exception. 来自ATOM的目标URI工作正常,但来自JSON的目标URI总是抛出异常。

Instead of manually concatenating the URI, is there a way to have operation descriptors working when using both formats (ATOM and JSON)? 而不是手动连接URI,有没有办法让操作描述符在使用两种格式(ATOM和JSON)时工作?

Note that I'm experiencing the same issue with OData v4, but getting MyNamespace.MyAction as the Title and Target URI instead of MyContainer.MyAction. 请注意,我遇到了与OData v4相同的问题,但是将MyNamespace.MyAction作为标题和目标URI而不是MyContainer.MyAction。

I was able to reproduce this issue and is a bug in the client side. 我能够重现这个问题,并且是客户端的一个错误。 The only workaround I found is extend the MyContainer class to provide a strongly typed method that invokes the action: 我找到的唯一解决方法是扩展MyContainer类以提供调用操作的强类型方法:

namespace <NAMESPACE_OF_MYCONTAINER_CLASS>
{
    public partial class MyContainer
    {
        public double MyAction(Guid id)
        {
            Uri actionUri = new Uri(this.BaseUri,
                String.Format("MyEntities(guid'{0}')/MyAction", id)
                );

            return this.Execute<string>(actionUri, 
                "POST", true).First();
        }
    }
} 

as described here . 作为描述在这里 I have tracked this problem and seems old, I found this post where a guy (Uffe Lauesen) explains in him second post some extrange behavior when he reads the Title (instead of Target) property of ActionDescriptor class when json format is used. 我已经跟踪了这个问题并且看起来很老了,我发现这篇帖子中有一个人(Uffe Lauesen)在使用json格式时读取ActionDescriptor类的Title(而不是Target)属性时在他的第二篇文章中解释了一些外部行为。

You still open a issue with odata.net in their github page. 你仍然在他们的github页面中打开odata.net的问题

UPDATE: 更新:

I tracked this issue, the Atom format uses NoOpEntityMetadataBuilder which returns non computed actions (with atom format it parses the xml and get the actions from feed). 我跟踪了这​​个问题,Atom格式使用NoOpEntityMetadataBuilder返回非计算操作(使用原子格式解析xml并从feed获取操作)。

internal override IEnumerable<ODataAction> GetActions()
{
    DebugUtils.CheckNoExternalCallers();
    return this.entry.NonComputedActions;
}

instead Json format uses ODataConventionalEntityMetadataBuilder which returns computed actions concatenated with non-computed actions: 相反,Json格式使用ODataConventionalEntityMetadataBuilder,它返回与非计算操作连接的计算操作:

internal override IEnumerable<ODataAction> GetActions()
{
    DebugUtils.CheckNoExternalCallers();
    return ODataUtilsInternal.ConcatEnumerables(this.entryMetadataContext.Entry.NonComputedActions, this.MissingOperationGenerator.GetComputedActions());
}

for computed actions we ended calling this extension function in EdmLibraryExtensions: 对于计算操作,我们在EdmLibraryExtensions中调用此扩展函数:

internal static string FullName(this IEdmEntityContainerElement containerElement)
{
    Debug.Assert(containerElement != null, "containerElement != null");

    return containerElement.Container.Name + "." + containerElement.Name;
}

so I believe here is better no return the container.Name, only the containerElement.Name. 所以我相信这里更好的是没有返回container.Name,只有containerElement.Name。 Running a patched version of Microsoft OData Library with this minimal change can be avoid the problem until the issue is resolved in github and official version is published. 使用此最小更改运行修补版本的Microsoft OData Library可以避免此问题,直到在github中解决该问题并发布正式版本。

As of today, the issue in the OData/odata.net github has been assigned to someone but there's still no news. 截至今天,OData / odata.net github中的问题已经分配给某人,但仍然没有消息。

I decided to write a custom OData path handler to support JSON action names. 我决定编写一个自定义OData路径处理程序来支持JSON操作名称。 It's working "for me" with the following OData path templates: ~/action, ~/entityset/key/action and ~/entityset/action. 它使用以下OData路径模板“为我”工作:〜/ action,〜/ entityset / key / action和〜/ entityset / action。

CustomODataPathHandler.cs CustomODataPathHandler.cs

internal class CustomODataPathHandler : DefaultODataPathHandler
{
    #region Methods

    protected override ODataPathSegment ParseAtEntityCollection(IEdmModel model, ODataPathSegment previous, IEdmType previousEdmType, string segment)
    {
        ODataPathSegment customActionPathSegment;
        if (TryParseCustomAction(model, previousEdmType, segment, out customActionPathSegment))
        {
            return customActionPathSegment;
        }

        return base.ParseAtEntityCollection(model, previous, previousEdmType, segment);
    }

    protected override ODataPathSegment ParseAtEntity(IEdmModel model, ODataPathSegment previous, IEdmType previousEdmType, string segment)
    {
        ODataPathSegment customActionPathSegment;
        if (TryParseCustomAction(model, previousEdmType, segment, out customActionPathSegment))
        {
            return customActionPathSegment;
        }

        return base.ParseAtEntity(model, previous, previousEdmType, segment);
    }

    protected override ODataPathSegment ParseEntrySegment(IEdmModel model, string segment)
    {
        var container = model.EntityContainers().First();
        if (CouldBeCustomAction(container, segment))
        {
            ODataPathSegment customActionPathSegment;
            if (TryParseCustomAction(model, segment, out customActionPathSegment))
            {
                return customActionPathSegment;
            }
        }

        return base.ParseEntrySegment(model, segment);
    }

    private static bool TryParseCustomAction(IEdmModel model, IEdmType previousEdmType, string segment, out ODataPathSegment pathSegment)
    {
        var container = model.EntityContainers().First();
        if (CouldBeCustomAction(container, segment))
        {
            var actionName = segment.Split('.').Last();
            var action = (from f in container.FindFunctionImports(actionName)
                          let parameters = f.Parameters
                          where parameters.Count() >= 1 && parameters.First().Type.Definition.IsEquivalentTo(previousEdmType)
                          select f).FirstOrDefault();

            if (action != null)
            {
                pathSegment = new ActionPathSegment(action);
                return true;
            }
        }

        pathSegment = null;
        return false;
    }

    private static bool TryParseCustomAction(IEdmModel model, string segment, out ODataPathSegment pathSegment)
    {
        var container = model.EntityContainers().First();
        if (CouldBeCustomAction(container, segment))
        {
            var actionName = segment.Split('.').Last();
            var action = (from f in container.FindFunctionImports(actionName)
                          where f.EntitySet == null && !f.IsBindable
                          select f).FirstOrDefault();

            if (action != null)
            {
                pathSegment = new ActionPathSegment(action);
                return true;
            }
        }

        pathSegment = null;
        return false;
    }

    private static bool CouldBeCustomAction(IEdmEntityContainer container, string segment)
    {
        return segment.StartsWith(container.Name + ".", StringComparison.OrdinalIgnoreCase);
    }

    #endregion
}

Note that since JSON action names contain a dot ".", I had to add a handler in the web.config (to avoid conflicting with the static file handler): 请注意,由于JSON操作名称包含点“。”,因此我必须在web.config中添加一个处理程序(以避免与静态文件处理程序冲突):

web.config web.config中

<system.webServer>
  <handlers>
    <add name="UrlRoutingHandler" path="odata/*" verb="*" type="System.Web.Routing.UrlRoutingHandler, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
  </handlers>
</system.webServer>

Also, the WebApiConfig changed to use the custom OData path handler: 此外,WebApiConfig更改为使用自定义OData路径处理程序:

WebApiConfig.cs WebApiConfig.cs

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        var modelBuilder = new ODataConventionModelBuilder();
        modelBuilder.Namespace = "MyNamespace";
        modelBuilder.ContainerName = "MyContainer";
        modelBuilder.EntitySet<MyEntity>("MyEntities");

        var action = modelBuilder.Entity<MyEntity>().Action("MyAction");
        action.Returns<string>();

        modelBuilder.Action("Test");

        foreach (var structuralType in modelBuilder.StructuralTypes)
        {
            // Resets the namespace so that the service contains only 1 namespace.
            structuralType.GetType().GetProperty("Namespace").SetValue(structuralType, "MyNamespace");
        }

        var model = modelBuilder.GetEdmModel();
        config.Routes.MapODataServiceRoute("OData", "odata", model, new CustomODataPathHandler(), ODataRoutingConventions.CreateDefault());
    }
}

Please take a look at the web.config of your service. 请查看您的服务的web.config。 Please update path="*." 请更新path =“*。” in the following line to path="odata/*" . 在以下行中路径=“odata / *” This is to handle dot in url path. 这是为了处理url路径中的点。

<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM