Search code examples
c#asp.net-coreodataaspnetboilerplate

How to control response wrapping in ABP on a per-route basis?


I'm using ABP (aspnetboilerplate) 7.0 thru ASP.NET Zero 11 and I'm trying to get OData working. I've followed the article over at ABP and I've taken inspiration from their sample.

The response of OData routes (/odata and /odata/$metadata) should not be wrapped. ABP does provide an attribute to prevent wrapping called DontWrapResult. However, since these routes are not on controllers that I have direct access to, I can't set the attribute.

The same question has been asked here: Disable Wrapping of Controller Results
However, they wanted to disable wrapping altogether, which is not what I want to do.

The answer to that question is to use a ResultFilter to set the attribute's value. I have, however, found that setting the value thru the attribute also sets the value that comes from the injected IAbpAspNetCoreConfiguration.

For example:

public class ODataResultFilter : IResultFilter, ITransientDependency
{
    private readonly IAbpAspNetCoreConfiguration _configuration;

    public ODataResultFilter(IAbpAspNetCoreConfiguration configuration)
    {
        _configuration = configuration;
    }

    public void OnResultExecuting(ResultExecutingContext context)
    {
        var methodInfo = context.ActionDescriptor.GetMethodInfo();

        var wrapResultAttribute =
            GetSingleAttributeOfMemberOrDeclaringTypeOrDefault(
                methodInfo,
                _configuration.DefaultWrapResultAttribute,
                false
            );

        if (context.HttpContext.Request.Path.Value.Equals("/odata/$metadata") ||
            context.HttpContext.Request.Path.Value.Equals("/odata"))
        {
            wrapResultAttribute.WrapOnSuccess = false;
        }
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // No action
    }

    private TAttribute GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<TAttribute>(MemberInfo memberInfo, TAttribute defaultValue = default(TAttribute), bool inherit = true)
        where TAttribute : class
    {
        return memberInfo.GetCustomAttributes(true).OfType<TAttribute>().FirstOrDefault()
               ?? memberInfo.DeclaringType?.GetTypeInfo().GetCustomAttributes(true).OfType<TAttribute>().FirstOrDefault()
               ?? defaultValue;
    }
}

As soon as I hit wrapResultAttribute.WrapOnSuccess = false;, _configuration.DefaultWrapResultAttribute becomes false and every other request ends up not being wrapped. My front-end expects wrapped responses and thus the front-end stops working as soon as I hit an OData route once.

How can I manipulate this attribute and prevent wrapping for OData routes but leave the default + attribute-configured wrapping behavior for the other routes?

GetSingleAttributeOfMemberOrDeclaringTypeOrDefault method should work fine, except right now, since _configuration.DefaultWrapResultAttribute gets modified, a controller that doesn't explicitly set a WrapResult attribute will get the default, overridden by the last value set.


Solution

  • Implement IWrapResultFilter, which was introduced in ABP v6.5:

    using Abp.Web.Results.Filters;
    using System;
    
    namespace AbpODataDemo.Web.Host.ResultWrapping
    {
        public class ODataWrapResultFilter : IWrapResultFilter
        {
            public bool HasFilterForWrapOnError(string url, out bool wrapOnError)
            {
                wrapOnError = false;
                return new Uri(url).AbsolutePath.StartsWith("/odata", StringComparison.InvariantCultureIgnoreCase);
            }
    
            public bool HasFilterForWrapOnSuccess(string url, out bool wrapOnSuccess)
            {
                wrapOnSuccess = false;
                return new Uri(url).AbsolutePath.StartsWith("/odata", StringComparison.InvariantCultureIgnoreCase);
            }
        }
    }
    

    Add it to WrapResultFilters in the PreInitialize method of your module:

    Configuration.Modules.AbpWebCommon().WrapResultFilters.Add(new ODataWrapResultFilter());
    

    Reference:


    OData with ABP v7.1 and later

    Abp.AspNetCore.OData implements AbpODataDontWrapResultFilter to disable result wrapping for paths that start with "/odata".

    Add it to WrapResultFilters in the PreInitialize method of your module:

    Configuration.Modules.AbpWebCommon().WrapResultFilters.Add(new AbpODataDontWrapResultFilter());
    

    The rationale of letting library users configure this explicitly is to highlight this interaction between the default result wrapping and OData use case.

    References: