栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > .Net

ASP.NET Core Web API 在.NET 5.0下的JSONP实现

.Net 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

ASP.NET Core Web API 在.NET 5.0下的JSONP实现

最近在做.NET Framework 到 .NET 5.0 的升级,其中遇到了一个重要的问题就是跨域请求的问题。因为老项目中主要采用的是JSONP的方式实现跨域请求,因此为了保证对老系统的支持,我们仍需实现对JSONP的支持。但是在实际升级过程中,却发现现有关于.NET 5.0下JSONP实现的文章少之又少,少有的几篇文章,经尝试后发现仅能运用于.NET Core 3.1及之前的版本,而不再适用于.NET 5.0 框架。因此我在查阅官方文档以及不断尝试后,寻找出了一个方法,在此记录分享,也希望有大佬能给我提出意见建议或是更好的实现方法。

写在前面
  1. 微软在.NET 5.0中更推荐采用 (CORS) 启用跨域请求 ASP.NET Core。如果不是必须采用JSONP,大家可以考虑改用 跨域资源共享(CORS) 来实现跨域。
  2. 对于.NET Core 3.1及之前的版本,或者是采用CORS方式的,可以直接参考文章:Asp.Net Core 中的跨域解决方案(Cors、jsonp改造、chrome配置)
使用自定义格式化程序实现JSONP

本文参考官方文档ASP.NET Core Web API 中的自定义格式化程序实现

1、创建JSONP格式化程序类

首先,我们需要先创建一个JSONP的格式化程序类JsonpMediaTypeFormatter,该类派生自TextOutputFormatter。

public class JsonpMediaTypeFormatter : TextOutputFormatter
{
    public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
    {
        throw new NotImplementedException();
    }
}
2、在构造函数中指定有效的媒体类型和编码

我们需要在JsonpMediaTypeFormatter 的构造函数中指定有效的媒体类型和编码。

private static readonly MediaTypeHeaderValue JsonType = new MediaTypeHeaderValue("application/json");
private static readonly MediaTypeHeaderValue JsType = new MediaTypeHeaderValue("application/javascript");
private static readonly MediaTypeHeaderValue TextType = new MediaTypeHeaderValue("text/javascript");

public JsonpMediaTypeFormatter()
{
    SupportedMediaTypes.Add(JsonType);
    SupportedMediaTypes.Add(JsType);
    SupportedMediaTypes.Add(TextType);

    SupportedEncodings.Add(Encoding.UTF8);
    SupportedEncodings.Add(Encoding.Unicode);
}
3、指定使用JSONP格式的条件

我们需要通过重写 CanWriteType 方法来指定何时启动JSONP格式对返回数据进行处理。通常,我们在采用GET请求,callback数非空时,实现对JSONP请求的响应。

public override bool CanWriteResult(OutputFormatterCanWriteContext context)
{
    if (context.HttpContext.Request.Method != HttpMethods.Get)
    {
        return false;
    }

    string callback = HttpUtility.UrlDecode((string)context.HttpContext.Request.Query["callback"]);
    return !string.IsNullOrEmpty(callback);
}
4、改造数据结果,实现JSONP格式

最后,只需要重写WriteResponseBodyAsync方法,即可根据callback参数,返回对应的JSONP格式数据。

public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
{
    string callback = HttpUtility.UrlDecode((string)context.HttpContext.Request.Query["callback"]);

    var buffer = new StringBuilder();
    buffer.Append(callback);
    buffer.Append('(');
    buffer.Append(JsonSerializer.Serialize(context.Object));
    buffer.Append(')');

    await context.HttpContext.Response.WriteAsync(buffer.ToString(), selectedEncoding);
}
5、注册JSONP格式化程序类

这时候要想正常使用JSONP格式,还差最后一步。我们需要在ConfigureServices中注册我们刚创建的JsonpMediaTypeFormatter。

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers(options =>
    {
        options.OutputFormatters.Insert(0, new JsonpMediaTypeFormatter());
    });
}
6、最后

至此,我们的Web API就可以支持JSONP请求啦,最后附上完整的JsonpMediaTypeFormatter代码。

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.Web;

namespace Formatter
{
    public class JsonpMediaTypeFormatter : TextOutputFormatter
    {
        private static readonly MediaTypeHeaderValue JsonType = new MediaTypeHeaderValue("application/json");
        private static readonly MediaTypeHeaderValue JsType = new MediaTypeHeaderValue("application/javascript");
        private static readonly MediaTypeHeaderValue TextType = new MediaTypeHeaderValue("text/javascript");

        public JsonpMediaTypeFormatter()
        {
            SupportedMediaTypes.Add(JsonType);
            SupportedMediaTypes.Add(JsType);
            SupportedMediaTypes.Add(TextType);

            SupportedEncodings.Add(Encoding.UTF8);
            SupportedEncodings.Add(Encoding.Unicode);
        }

        public override bool CanWriteResult(OutputFormatterCanWriteContext context)
        {
            if (context.HttpContext.Request.Method != HttpMethods.Get)
            {
                return false;
            }

            string callback = HttpUtility.UrlDecode((string)context.HttpContext.Request.Query["callback"]);
            return !string.IsNullOrEmpty(callback);
        }

        public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
        {
            string callback = HttpUtility.UrlDecode((string)context.HttpContext.Request.Query["callback"]);

            var buffer = new StringBuilder();
            buffer.Append(callback);
            buffer.Append('(');
            buffer.Append(JsonSerializer.Serialize(context.Object));
            buffer.Append(')');

            await context.HttpContext.Response.WriteAsync(buffer.ToString(), selectedEncoding);
        }
    }
}
附:失败的经历

在最初我是试图采用筛选器 ActionFilterAttribute ,通过重写
OnActionExecuted方法来实现对数据结果的调整。但在实际测试中发现,传入callback参数后会发生如下错误,故而放弃了采用该种方式实现JSONP的想法。

fail: Microsoft.AspNetCore.Server.Kestrel[13]
Connection id “0HM8UGOM59TCA”, Request id “0HM8UGOM59TCA:00000006”: An unhandled exception was thrown by the application.
System.InvalidOperationException: Headers are read-only, response has already started.
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders.ThrowHeadersReadOnlyException()
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders.Microsoft.AspNetCore.Http.IHeaderDictionary.set_Item(String key, StringValues value)
at Microsoft.AspNetCore.Http.DefaultHttpResponse.set_ContentType(String value)
at Microsoft.AspNetCore.Mvc.Formatters.OutputFormatter.WriteResponseHeaders(OutputFormatterWriteContext context)
at Microsoft.AspNetCore.Mvc.Formatters.TextOutputFormatter.WriteAsync(OutputFormatterWriteContext context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor.ExecuteAsyncCore(ActionContext context, ObjectResult result, Type objectType, Object value)
at Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor.ExecuteAsync(ActionContext context, ObjectResult result)
at Microsoft.AspNetCore.Mvc.ObjectResult.ExecuteResultAsync(ActionContext context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultAsync(IActionResult result)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeNextResultFilterAsyncTFilter,TFilterAsync
— End of stack trace from previous location —
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
— End of stack trace from previous location —
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/955002.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号