Override webapi odata link host

I use WebAPI 2.2 and Microsoft.AspNet.OData 5.7.0 to create an OData service that supports paging.

When hosted in a production environment, the WebAPI lives on a server that is not displayed externally, so various links returned in the OData response, such as @odata.contextand @odata.nextLink, point to an internal IP address, for example. http://192.168.X.X/<AccountName>/api/...etc.

I was able to change Request.ODataProperties().NextLinkby following some logic in each ODataController method to replace the internal URL with an external URL, for example https://account-name.domain.com/api/..., but this is very inconvenient and it only fixes NextLinks.

Is there any way to set the external host name while configuring the OData service? I saw a property Request.ODataProperties().Pathand I wonder if it is possible to set the base path when called config.MapODataServiceRoute("odata", "odata", GetModel());or in the implementation GetModel()using, for example ODataConventionModelBuilder,?


The UPDATE: . The best solution I've come up with so far is to create BaseODataControllerone that overrides the method Initializeand checks to see if there is Request.RequestUri.Host.StartsWith("beginning-of-known-internal-IP-address"), and then executes RequestUri, rewrite it like this:

var externalAddress = ConfigClient.Get().ExternalAddress;  // e.g. https://account-name.domain.com
var account = ConfigClient.Get().Id;  // e.g. AccountName
var uriToReplace = new Uri(new Uri("http://" + Request.RequestUri.Host), account);
string originalUri = Request.RequestUri.AbsoluteUri;
Request.RequestUri = new Uri(Request.RequestUri.AbsoluteUri.Replace(uriToReplace.AbsoluteUri, externalAddress));
string newUri = Request.RequestUri.AbsoluteUri;
this.GetLogger().Info($"Request URI was rewritten from {originalUri} to {newUri}");

This captures URLs perfectly @odata.nextLinkfor all controllers, but for some reason the URLs @odata.contextstill get the part AccountName(e.g. https://account-name.domain.com/AccountName/api/odata/ $ metadata # ControllerName) therefore they still do not work.

+2
5

RequestUri , @odata.nextLink, , RequestUri , @odata.xxx UrlHelper, - URI . (, AccountName @odata.context. , URI.)

RequestUri , CustomUrlHelper OData . GetNextPageLink @odata.nextLink, Link .

public class CustomUrlHelper : System.Web.Http.Routing.UrlHelper
{
    public CustomUrlHelper(HttpRequestMessage request) : base(request)
    { }

    // Change these strings to suit your specific needs.
    private static readonly string ODataRouteName = "ODataRoute"; // Must be the same as used in api config
    private static readonly string TargetPrefix = "http://localhost:8080/somePathPrefix"; 
    private static readonly int TargetPrefixLength = TargetPrefix.Length;
    private static readonly string ReplacementPrefix = "http://www.contoso.com"; // Do not end with slash

    // Helper method.
    protected string ReplaceTargetPrefix(string link)
    {
        if (link.StartsWith(TargetPrefix))
        {
            if (link.Length == TargetPrefixLength)
            {
                link = ReplacementPrefix;
            }
            else if (link[TargetPrefixLength] == '/')
            {
                link = ReplacementPrefix + link.Substring(TargetPrefixLength);
            }
        }

        return link;
    }

    public override string Link(string routeName, IDictionary<string, object> routeValues)
    {
        var link = base.Link(routeName, routeValues);

        if (routeName == ODataRouteName)
        {
            link = this.ReplaceTargetPrefix(link);
        }

        return link;
    }

    public Uri GetNextPageLink(int pageSize)
    {
        return new Uri(this.ReplaceTargetPrefix(this.Request.GetNextPageLink(pageSize).ToString()));
    }
}

CustomUrlHelper Initialize .

public abstract class BaseODataController : ODataController
{
    protected abstract int DefaultPageSize { get; }

    protected override void Initialize(System.Web.Http.Controllers.HttpControllerContext controllerContext)
    {
        base.Initialize(controllerContext);

        var helper = new CustomUrlHelper(controllerContext.Request);
        controllerContext.RequestContext.Url = helper;
        controllerContext.Request.ODataProperties().NextLink = helper.GetNextPageLink(this.DefaultPageSize);
    }

, . , ODataProperties().NextLink :

var helper = this.RequestContext.Url as CustomUrlHelper;
this.Request.ODataProperties().NextLink = helper.GetNextPageLink(otherPageSize);
+4

, url . :

  • owin Host Scheme

public class RewriteUrlMiddleware : OwinMiddleware
{
    public RewriteUrlMiddleware(OwinMiddleware next)
        : base(next)
    {
    }

    public override async Task Invoke(IOwinContext context)
    {
        context.Request.Host = new HostString(Settings.Default.ProxyHost);
        context.Request.Scheme = Settings.Default.ProxyScheme;
        await Next.Invoke(context);
    }
}

ProxyHost - , . : test.com

ProxyScheme - , : : https

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.Use(typeof(RewriteUrlMiddleware));
        var config = new HttpConfiguration();
        WebApiConfig.Register(config);
        app.UseWebApi(config);
    }
}
+2

, . , UrlHelper, , System.Net.Http.DelegatingHandler. () , , HttpRequestMessage. , URL- ( UrlHelper, , https://data.contoso.com/odata/MyController), URL-, xml: base OData (, https://data.contoso.com/odata).

OData -, , URL-, , -, -. ; , .

:

    public class BehindProxyMessageHandler : DelegatingHandler
    {
        protected async override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var builder = new UriBuilder(request.RequestUri);
            var visibleHost = builder.Host;
            var visibleScheme = builder.Scheme;
            var visiblePort = builder.Port;

            if (request.Headers.Contains("X-Forwarded-Host"))
            {
                string[] forwardedHosts = request.Headers.GetValues("X-Forwarded-Host").First().Split(new char[] { ',' });
                visibleHost = forwardedHosts[0].Trim();
            }

            if (request.Headers.Contains("X-Forwarded-Proto"))
            {
                visibleScheme = request.Headers.GetValues("X-Forwarded-Proto").First();
            }

            if (request.Headers.Contains("X-Forwarded-Port"))
            {
                try
                {
                    visiblePort = int.Parse(request.Headers.GetValues("X-Forwarded-Port").First());
                }
                catch (Exception)
                { }
            }

            builder.Host = visibleHost;
            builder.Scheme = visibleScheme;
            builder.Port = visiblePort;

            request.RequestUri = builder.Uri;
            var response = await base.SendAsync(request, cancellationToken);
            return response;
        }
    }

WebApiConfig.cs:

    config.Routes.MapODataServiceRoute(
        routeName: "odata",
        routePrefix: "odata",
        model: builder.GetEdmModel(),
        pathHandler: new DefaultODataPathHandler(),
        routingConventions: ODataRoutingConventions.CreateDefault()
    );
    config.MessageHandlers.Insert(0, new BehindProxyMessageHandler());
+1

system.web.odata 6.0.0.0.

NextLink , . Link . , , .

http://docs.oasis-open.org/odata/odata-json-format/v4.0/os/odata-json-format-v4.0-os.html#_Toc372793048 :

URL-, ( ) URL-.

, , , EnableQueryAttribute:

public class myEnableQueryAttribute : EnableQueryAttribute
{
    public override IQueryable ApplyQuery(IQueryable queryable, ODataQueryOptions queryOptions)
    {
        var result = base.ApplyQuery(queryable, queryOptions);
        var nextlink = queryOptions.Request.ODataProperties().NextLink;
        if (nextlink != null)
            queryOptions.Request.ODataProperties().NextLink = queryOptions.Request.RequestUri.MakeRelativeUri(nextlink);
        return result;
    }
}

ApplyQuery() - "". pagesize+1 NextLink, pagesize .

NextLink URL.

, odata myEnableQuery:

[myEnableQuery]
public async Task<IHttpActionResult> Get(ODataQueryOptions<TElement> options)
{
  ...
}

URL-, , . odata.context . URL- , , .

0

URI- . , , . ODataMediaTypeFormatter.MessageWriterSettings.PayloadBaseUri ODataMediaTypeFormatter.MessageWriterSettings.ODataUri.ServiceRoot , . , ODataMediaTypeFormatter WriteToStreamAsync.

, , IODataPathHandler.Link. - OData, , URI, .

public class CustomPathHandler : DefaultODataPathHandler
{
    private const string ServiceRoot = "http://example.com/";

    public override string Link(ODataPath path)
    {
        return ServiceRoot + base.Link(path);
    }
}

.

// config is an instance of HttpConfiguration
config.MapODataServiceRoute(
    routeName: "ODataRoute",
    routePrefix: null,
    model: builder.GetEdmModel(),
    pathHandler: new CustomPathHandler(),
    routingConventions: ODataRoutingConventions.CreateDefault()
);
-1

Source: https://habr.com/ru/post/1657970/


All Articles