1 注意:
在.NetCore WebApi框架中,在默认情况下由于没有集成“UseStaticFiles”内置管道中间件方法,如果想要通过图片URL显示图片,由会显示“404”错误,必须先把“UseStaticFiles”内置管道中间件方法集成到.NetCore WebApi框架中,这样就会解决该错误。
2 集成“UseStaticFiles”内置管道中间件方法
1 Framework.Infrastructure.Extensions.ApplicationBuilderExtensions.UseNopStaticFiles
/// <param name="application">.NetCore框架内置管道接口实例。</param>
/// <summary>
/// 【使用静态资源文件】
/// <remarks>
/// 摘要:
/// 把静态资源文件内置管道中间件实例集成到.NetCore框架内置管道中,为当前程序访问当前程序中的静态资源文件提供内置管道方法支撑。
/// </remarks>
/// </summary>
public static void UseNopStaticFiles(this IApplicationBuilder application)
{
application.UseStaticFiles();
}
2 Framework.Infrastructure.StaticFilesStartup
using Core.Infrastructure;
using Framework.Infrastructure.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Framework.Infrastructure
{
/// <summary>
/// 【静态资源文件启动--类】
/// <remarks>
/// 摘要:
/// 通过该类中的方法成员,把静态资源文件内置管道中间件实例集成到.NetCore框架内置管道中,为当前程序访问当前程序中的静态资源文件提供内置管道方法支撑。
/// </remarks>
/// </summary>
public class StaticFilesStartup : IStartup
{
#region 属性--接口实现
/// <summary>
/// 【顺序】
/// <remarks>
/// 摘要:
/// 获取跨域限制启动类中管道中间件实例,被集成到内置管道实例中的顺序,默认值:200。
/// 注意:
/// .NetCore框架默定义了一些常用的内置管道中间件,并默认规定了这些内置管道中间件实例在内置管道实例中被调用的顺序,
/// 如果不按照该默认顺序调用这些内置管道中间件,在程序执行时就会出现逻辑异常。
/// </remarks>
public int Order => 99; //静态资源文件内置管道中间件必须在路由内置管道中间件和自定义管道中间件之前之前被集成和调用,否则在程序执行时就会出现逻辑异常。
#endregion
#region 方法--接口实现
/// <param name="services">.Net(Core)框架内置依赖注入容器实例。</param>
/// <param name="configuration">.NetCore框架内置配置接口实例(存储着当前程序中所有*.json文件中的数据)。</param>
/// <summary>
/// 【配置服务】
/// <remarks>
/// 摘要:
/// 由于该类中所需要的实例,已经注入到内置容器中,所以该方法成员中没有依赖注入中间件/实例需要注入到内置依赖注入容器中。
/// </remarks>
/// </summary>
public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
}
/// <param name="application">.NetCore框架内置管道接口实例。</param>
/// <summary>
/// 【配置】
/// <remarks>
/// 摘要:
/// 该方法把静态资源文件内置管道中间件实例集成到.NetCore框架内置管道中。
/// </remarks>
/// </summary>
public void Configure(IApplicationBuilder application)
{
application.UseNopStaticFiles();
}
#endregion
}
}
3 Core.IWebHelper
using Microsoft.AspNetCore.Http;
namespace Core
{
/// <summary>
/// 【Web助手--接口】
/// <remarks>
/// 摘要:
/// 通过继承该接口具体实现类中的方法成员,获取客户端访问当前程序所使用的传输协议(HTTP/HTTPS)、客户端的IP地址、当前程序的根路由、指定页面路由及其相应查询键/值对字符串等。
/// </remarks>
public partial interface IWebHelper
{
#region 属性
/// <summary>
/// 【请求开始重定向?】
/// <remarks>
/// 摘要:
/// 设置1个值false(开始重定向)/true(未开始重定向),该值指示客户端浏览器请求是否是1个需要被重定向的请求;
/// 如果客户端浏览器所发送请求操作的状态码包含有“301/302”,则该请求就是1个需要被重定向的请求,为重定向跳转操作提供预先的数据支撑。
/// </remarks>
/// </summary>
bool IsRequestBeingRedirected { get; }
#endregion
#region 方法
/// <summary>
/// 【获取引用URL】
/// <remarks>
/// 摘要:
/// 该方法通过客户端浏览器请求头字典属性成员实例,来获取1次指定请求中所包含的1个指定引用URL字符串。
/// 引用URL的作用(https://blog.csdn.net/TanMengyi/article/details/109471246):
/// 1、防盗链
/// 2、防止恶意请求
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定引用URL字符串。
/// </returns>
/// </summary>
string GetUrlReferrer();
/// <summary>
/// 【获取当前IP地址】
/// <remarks>
/// 摘要:
/// 该方法通过客户端浏览器的1次指定请求,获取该客户端的IP地址字符串。
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定客户端的IP地址字符串。
/// </returns>
/// </summary>
string GetCurrentIpAddress();
/// <param name="includeQueryString">指示是否为1个指定URL拼接1指定的查询字符串。</param>
/// <param name="useSsl">指示对当前程序进行访问时是否使用HTTPS传输协议,默认值:null,即使用HTTPS传输协议。</param>
/// <param name="lowercaseUrl">指示是否把指定URL中的字母全部转接为小写字母,默认值为:false,即不把指定页面的URL中的字母全部转接为小写字母。</param>
/// <summary>
/// 【获取指定页面URL】
/// <remarks>
/// 摘要:
/// 通过该方法获取1个指定的URL字符串,该URL字符串为当前程序中指定页面的渲染显示提供预先的数据支撑。
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定的URL字符串。
/// </returns>
/// </summary>
string GetThisPageUrl(bool includeQueryString, bool? useSsl = null, bool lowercaseUrl = false);
/// <summary>
/// 【当前连接安全?】
/// <remarks>
/// 摘要:
/// 获取1个值false(不安全)/true(安全),该值指示客户端浏览器请求对当前程序的请求操作是否使用的是HTTPS传输协议,如果是,则当前连接是安全的。
/// </remarks>
/// <returns>
/// 返回:
/// 1个值false(是静态资源)/true(不是静态资源)
/// </returns>
/// </summary>
bool IsCurrentConnectionSecured();
/// <param name="useSsl">指示对当前程序进行访问时是否使用HTTPS传输协议,默认值:true,即使用HTTPS传输协议。</param>
/// <summary>
/// 【获取当前程序根路由】
/// <remarks>
/// 摘要:
/// 从客户端浏览器中获取对当前程序访问的根路由字符串(注意:根路由字符串的最后包含“/”字符)。
/// </remarks>
/// <returns>
/// 返回:
/// 当前程序的的根路由字符串(注意:根路由字符串的最后包含“/”字符)。
/// </returns>
/// </summary>
string GetStoreHost(bool useSsl = true);
/// <summary>
/// 【静态资源?】
/// <remarks>
/// 摘要:
/// 获取1个值false(是静态资源)/true(不静态资源),该值指示客户端浏览器请求的是否是静态资源
/// (一般情况下“wwwroot”文件夹中的持久化存储的文件都是静态资源;例如:https://localhost:7239/images/Avatar/Default.bmp,该请求就是对静态资源的1次请求)。
/// </remarks>
/// <returns>
/// 返回:
/// 1个值false(是静态资源)/true(不是静态资源)
/// </returns>
/// </summary>
bool IsStaticResource();
/// <param name="url">1个指定的URL字符串。</param>
/// <param name="key">1个指定的URL查询键/值字符串中的1个指定键。</param>
/// <param name="values">数组实例,该实例存储着1个指定的URL查询键/值字符串中的1个指定键所对应的1/多个值(每个值之间用“,”分割)。</param>
/// <summary>
/// 【修改查询字符串】
/// <remarks>
/// 摘要:
/// 对1个指定的URL查询键/值字符串中的1个指定键所对应的1/多个值进行修改更新操作(每个值之间用“,”分割)。
/// </remarks>
/// <returns>
/// 返回:
/// 被修改更新了查询键/值字符串后的1个指定的绝对路径URL字符串(该绝对路径URL字符串即可能是本地的(当前程序的),可能是第3方服务平台中的)。
/// </returns>
/// </summary>
string ModifyQueryString(string url, string key, params string[] values);
/// <param name="url">1个指定的URL字符串。</param>
/// <param name="key">1个指定的URL查询键/值字符串中的1个指定键。</param>
/// <param name="value">数该实例存储着1个指定的URL查询键/值字符串中的1个指定键所对应的1个值,默认值:null,即移除URL查询键/值字符串中的1个指定键/值对项。</param>
/// <summary>
/// 【移除查询字符串】
/// <remarks>
/// 摘要:
/// 对1个指定的URL查询键/值字符串中的1个指定键所对应的1/多个值进行修改更新操作(每个值之间用“,”分割)。
/// </remarks>
/// <returns>
/// 返回:
/// 被修改更新了查询键/值字符串后的1个指定的绝对路径URL字符串(该绝对路径URL字符串即可能是本地的(当前程序的),可能是第3方服务平台中的)。
/// </returns>
/// </summary>
string RemoveQueryString(string url, string key, string value = null);
/// <typeparam name="T">泛型类型实例(1个指定类的类型实例)。</typeparam>
/// <param name="name">URL查询键/值字符串中的1个指定键。</param>
/// <summary>
/// 【查询字符串】
/// <remarks>
/// 摘要:
/// 该方法以泛型形式把URL查询键/值字符串中字符串类型的1个指定值转换为1个指定类型的1个指定实例。
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定类型的1个指定实例。
/// </returns>
/// </summary>
T QueryString<T>(string name);
/// <summary>
/// 【重置应用域】
/// <remarks>
/// 摘要:
/// 该方法为客户端浏览器的自动重启操作提供方法支撑。
/// 说明:
/// 例如:在当前程序第1次数据库的初始化安装成功完成后,客户端浏览器就会通过该方法来自动关闭数据库的初始化安装页面,
/// 从而使当程序重新加载所有的初始化数据,保证当前程序能够在客户端浏览器顺序的被运行。
/// </remarks>
/// </summary>
void RestartAppDomain();
/// <summary>
/// 【获取当前请求协议】
/// <remarks>
/// 摘要:
/// 从客户端浏览器中获取对当前程序进行请求访问时所使用的传输协议字符串:"http"/"https"。
/// </remarks>
/// <returns>
/// 返回:
/// 传输协议字符串:"http"/"https"。
/// </returns>
/// </summary>
string GetCurrentRequestProtocol();
/// <param name="request">HTTP请求类的1个指定实例。</param>
/// <summary>
/// 【本地请求?】
/// <remarks>
/// 摘要:
/// 获取1个值false(通过代理)/true(未通过代理),该值指示客户端对当前程序的访问是否通过了代理软件或服务(例如:Nginx等,实际上前后端分离的中的跨域中间件实质上也是一种代理访问)。
/// 说明:
/// 如果是本地请求“LocalIpAddress”与“RemoteIpAddress”是相等的;如果是代理请求“LocalIpAddress”与“RemoteIpAddress”是不等的。
/// </remarks>
/// <returns>
/// 返回:
/// 1个值false(通过代理)/true(未通过代理)。
/// </returns>
/// </summary>
bool IsLocalRequest(HttpRequest request);
/// <param name="request">HTTP请求类的1个指定实例。</param>
/// <summary>
/// 【获取原URL】
/// <remarks>
/// 摘要:
/// 该方法主要为在执行登录操作成功后返回原来的指定页面,提供方法支撑。
/// </remarks>
/// <returns>
/// 返回:
/// 执行登录操作成功后返回原来的指定页面的URL字符串。
/// </returns>
/// </summary>
string GetRawUrl(HttpRequest request);
/// <param name="request">HTTP请求类的1个指定实例。</param>
/// <summary>
/// 【Ajax请求?】
/// <remarks>
/// 摘要:
/// 获取1个值false(不是通过Ajax脚笨命令进行触发的请求)/true(是通过Ajax脚笨命令进行触发的请求),该值指示客户端浏览器请求头字典属性成员实例中是否包含有Ajax请求值,如果有则客户端的请求是通过Ajax脚笨命令进行触发的请求。
/// </remarks>
/// <returns>
/// 返回:
/// 1个值false(不是通过Ajax脚笨命令进行触发的请求)/true(是通过Ajax脚笨命令进行触发的请求)。
/// </returns>
/// </summary>
bool IsAjaxRequest(HttpRequest request);
#endregion
}
}
4 Core.WebHelper
using System.Net;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
namespace Core
{
/// <summary>
/// 【Web助手--类】
/// <remarks>
/// 摘要:
/// 通过该类中的方法成员,获取客户端访问当前程序所使用的传输协议(HTTP/HTTPS)、客户端的IP地址、当前程序的根路由、指定页面路由及其相应查询键/值对字符串等。
/// </remarks>
public partial class WebHelper : IWebHelper
{
#region 拷贝构造方法与变量
private readonly IActionContextAccessor _actionContextAccessor;
private readonly IHostApplicationLifetime _hostApplicationLifetime;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IUrlHelperFactory _urlHelperFactory;
/// <summary>
/// 【拷贝构建方法】
/// <remarks>
/// 摘要:
/// 依赖注入容器通过拷贝构造方法,实例化该类中的变量成员。
/// </remarks>
/// </summary>
public WebHelper(IActionContextAccessor actionContextAccessor,
IHostApplicationLifetime hostApplicationLifetime,
IHttpContextAccessor httpContextAccessor,
IUrlHelperFactory urlHelperFactory)
{
_actionContextAccessor = actionContextAccessor;
_hostApplicationLifetime = hostApplicationLifetime;
_httpContextAccessor = httpContextAccessor;
_urlHelperFactory = urlHelperFactory;
}
#endregion
#region 属性
/// <summary>
/// 【请求开始重定向?】
/// <remarks>
/// 摘要:
/// 设置1个值false(开始重定向)/true(未开始重定向),该值指示客户端浏览器请求是否是1个需要被重定向的请求;
/// 如果客户端浏览器所发送请求操作的状态码包含有“301/302”,则该请求就是1个需要被重定向的请求,为重定向跳转操作提供预先的数据支撑。
/// </remarks>
/// </summary>
public virtual bool IsRequestBeingRedirected
{
get
{
//获取1次的HTTP请求上下文的请求属性成员实例。
var response = _httpContextAccessor.HttpContext.Response;
//ASP.NET 4 style - return response.IsRequestBeingRedirected;
//实例化1个包含有“301/302”状态码的数组实例。
int[] redirectionStatusCodes = { StatusCodes.Status301MovedPermanently, StatusCodes.Status302Found };
//如果数组实例中包含有1次的HTTP请求上下文的请求属性成员实例的状态码,则请求就是1个需要被重定向的请求。
return redirectionStatusCodes.Contains(response.StatusCode);
}
}
#endregion
#region 方法--私有/保护
/// <summary>
/// 【有效请求?】
/// <remarks>
/// 摘要:
/// 获取1个值false(有效)/true(无效),该值指示是否可以通过客户端浏览器来获取HTTP上下文实例,或HTTP上下文的请求属性成员实例,来验证1个指定的请求操作是否有效。
/// </remarks>
/// <returns>
/// 返回:
/// 1个值false(失败)/true(成功)
/// </returns>
/// </summary>
protected virtual bool IsRequestAvailable()
{
//如果客户端浏览器不能获取HTTP上下文实例,则该指定的请求操作是无有效的。
if (_httpContextAccessor?.HttpContext == null)
return false;
try
{
//如果客户端浏览器不能获取HTTP上下文的请求属性成员实例,则该指定的请求操作是无有效的。
if (_httpContextAccessor.HttpContext.Request == null)
return false;
}
catch (Exception)
{
return false;
}
return true;
}
/// <param name="address">1个指定的IP地址实例。</param>
/// <summary>
/// 【IP地址设置?】
/// <remarks>
/// 摘要:
/// 获取1个值false(IP地址设置不成立)/true(IP地址设置成立),该值指1个指定的IP地址实例是否不为空且不与IPv6规范实例相匹配,则IP地址设置成立。
/// 说明:
/// 要实现IPAddress.IPv6Loopback与IPv4的自动匹配,计算机设备和网络设备在软/硬件环境都必须支持IPAddress.IPv6Loopback。
/// </remarks>
/// <returns>
/// 返回:
/// 1个值false(IP地址设置不成立)/true(IP地址设置成立)。
/// </returns>
/// </summary>
protected virtual bool IsIpAddressSet(IPAddress address)
{
var rez = address != null && address.ToString() != IPAddress.IPv6Loopback.ToString();
return rez;
}
#endregion
#region 方法--接口实现
/// <summary>
/// 【获取引用URL】
/// <remarks>
/// 摘要:
/// 该方法通过客户端浏览器请求头字典属性成员实例,来获取1次指定请求中所包含的1个指定引用URL字符串。
/// 引用URL的作用(https://blog.csdn.net/TanMengyi/article/details/109471246):
/// 1、防盗链
/// 2、防止恶意请求
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定引用URL字符串。
/// </returns>
/// </summary>
public virtual string GetUrlReferrer()
{
if (!IsRequestAvailable())
return string.Empty;
//通过客户端浏览器请求头字典属性成员实例,来获取1次指定请求中所包含的引用URL字符串。
return _httpContextAccessor.HttpContext.Request.Headers[HeaderNames.Referer];
}
/// <summary>
/// 【获取当前IP地址】
/// <remarks>
/// 摘要:
/// 该方法通过客户端浏览器的1次指定请求,获取该客户端的IP地址字符串。
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定客户端的IP地址字符串。
/// </returns>
/// </summary>
public virtual string GetCurrentIpAddress()
{
if (!IsRequestAvailable())
return string.Empty;
//如果请求上下文的远程IP地址属性成员实例不与IP地址规范实例相匹配,则直接返回空字符串。
if (_httpContextAccessor.HttpContext.Connection?.RemoteIpAddress is not IPAddress remoteIp)
return "";
//如果远程IP地址属性成员实例与IPv6。规范实例相匹配,则返回该客户端的IP地址字符串。
//说明:
// 1、IPAddress.IPv6Loopback,包含IPv4。
// 2、要实现IPAddress.IPv6Loopback与IPv4的自动匹配,客户端的计算机设备和网络设备在软/硬件环境都必须支持IPAddress.IPv6Loopback。
if (remoteIp.Equals(IPAddress.IPv6Loopback))
return IPAddress.Loopback.ToString();
//如果客户端的计算机设备和网络设备在软/硬件方面不支持IPAddress.IPv6Loopback,则通过IPv4规范返回客户端的IP地址字符串。
return remoteIp.MapToIPv4().ToString();
}
/// <param name="includeQueryString">指示是否为1个指定URL拼接1指定的查询字符串。</param>
/// <param name="useSsl">指示对当前程序进行访问时是否使用HTTPS传输协议,默认值:null,即使用HTTPS传输协议。</param>
/// <param name="lowercaseUrl">指示是否把指定URL中的字母全部转接为小写字母,默认值为:false,即不把指定页面的URL中的字母全部转接为小写字母。</param>
/// <summary>
/// 【获取指定页面URL】
/// <remarks>
/// 摘要:
/// 通过该方法获取1个指定的URL字符串,该URL字符串为当前程序中指定页面的渲染显示提供预先的数据支撑。
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定的URL字符串。
/// </returns>
/// </summary>
public virtual string GetThisPageUrl(bool includeQueryString, bool? useSsl = null, bool lowercaseUrl = false)
{
if (!IsRequestAvailable())
return string.Empty;
//获取根路由字符串。
var storeLocation = GetStoreHost(useSsl ?? IsCurrentConnectionSecured());
//通过根路由字符串与指定控制器行为方法的字符串拼接出1个指定页面的URL字符串。
var pageUrl = $"{storeLocation.TrimEnd('/')}{_httpContextAccessor.HttpContext.Request.Path}";
//在1个指定页面的URL字符串添加拼接查询字符。
if (includeQueryString)
pageUrl = $"{pageUrl}{_httpContextAccessor.HttpContext.Request.QueryString}";
//把1个指定页面的URL字符串中的字母全部转换为小写。
if (lowercaseUrl)
pageUrl = pageUrl.ToLowerInvariant();
return pageUrl;
}
/// <summary>
/// 【当前连接安全?】
/// <remarks>
/// 摘要:
/// 获取1个值false(不安全)/true(安全),该值指示客户端浏览器请求对当前程序的请求操作是否使用的是HTTPS传输协议,如果是,则当前连接是安全的。
/// </remarks>
/// <returns>
/// 返回:
/// 1个值false(是静态资源)/true(不是静态资源)
/// </returns>
/// </summary>
public virtual bool IsCurrentConnectionSecured()
{
if (!IsRequestAvailable())
return false;
return _httpContextAccessor.HttpContext.Request.IsHttps;
}
/// <param name="useSsl">指示对当前程序进行访问时是否使用HTTPS传输协议,默认值:true,即使用HTTPS传输协议。</param>
/// <summary>
/// 【获取当前程序根路由】
/// <remarks>
/// 摘要:
/// 从客户端浏览器中获取对当前程序访问的根路由字符串(注意:根路由字符串的最后包含“/”字符)。
/// </remarks>
/// <returns>
/// 返回:
/// 当前程序的的根路由字符串(注意:根路由字符串的最后包含“/”字符)。
/// </returns>
/// </summary>
public virtual string GetStoreHost(bool useSsl = true)
{
if (!IsRequestAvailable())
return string.Empty;
//客户端浏览器中获取对当前程序访问的根路由。
var hostHeader = _httpContextAccessor.HttpContext.Request.Headers[HeaderNames.Host];
if (StringValues.IsNullOrEmpty(hostHeader))
return string.Empty;
//把传输协议与根路由结合拼接出根路由字符串。
//Uri.UriSchemeHttps:"https"
//Uri.UriSchemeHttp:"http"
//Uri.SchemeDelimiter:"://"
var storeHost = $"{(useSsl ? Uri.UriSchemeHttps : Uri.UriSchemeHttp)}{Uri.SchemeDelimiter}{hostHeader.FirstOrDefault()}";
//确保根路由字符串的最后包含“/”字符。
storeHost = $"{storeHost.TrimEnd('/')}/";
return storeHost;
}
/// <summary>
/// 【静态资源?】
/// <remarks>
/// 摘要:
/// 获取1个值false(是静态资源)/true(不静态资源),该值指示客户端浏览器请求的是否是静态资源
/// (一般情况下“wwwroot”文件夹中的持久化存储的文件都是静态资源;例如:https://localhost:7239/images/Avatar/Default.bmp,该请求就是对静态资源的1次请求)。
/// </remarks>
/// <returns>
/// 返回:
/// 1个值false(是静态资源)/true(不是静态资源)
/// </returns>
/// </summary>
public virtual bool IsStaticResource()
{
if (!IsRequestAvailable())
return false;
string path = _httpContextAccessor.HttpContext.Request.Path;
//通过FileExtensionContentTypeProvider实例,来判断一个指定请求的路径字符串所对应的服务器端中的文件,是否为静态资源文件。
//FileExtensionContentTypeProvider实例定义了,大量常用静态资源文件的扩展名,该实例通过扩展名来检索一个文件,是否为静态资源文件。
//原代码见: https://github.com/aspnet/StaticFiles/tree/master/src/Microsoft.AspNetCore.StaticFiles/FileExtensionContentTypeProvider.cs。
var contentTypeProvider = new FileExtensionContentTypeProvider();
return contentTypeProvider.TryGetContentType(path, out var _);
}
/// <param name="url">1个指定的URL字符串。</param>
/// <param name="key">1个指定的URL查询键/值字符串中的1个指定键。</param>
/// <param name="values">数组实例,该实例存储着1个指定的URL查询键/值字符串中的1个指定键所对应的1/多个值(每个值之间用“,”分割)。</param>
/// <summary>
/// 【修改查询字符串】
/// <remarks>
/// 摘要:
/// 对1个指定的URL查询键/值字符串中的1个指定键所对应的1/多个值进行修改更新操作(每个值之间用“,”分割)。
/// </remarks>
/// <returns>
/// 返回:
/// 被修改更新了查询键/值字符串后的1个指定的绝对路径URL字符串(该绝对路径URL字符串即可能是本地的(当前程序的),可能是第3方服务平台中的)。
/// </returns>
/// </summary>
public virtual string ModifyQueryString(string url, string key, params string[] values)
{
if (string.IsNullOrEmpty(url))
return string.Empty;
if (string.IsNullOrEmpty(key))
return url;
//通过当前程序中的指定控制器的指定行为方法,来验证1个指定的URL字符串,是否是相对路径URL字符串,也可以这样理解相对路径URL字符串一定是本地的(当前程序的),绝对路径URL字符串可能是本地的也可能是第3方服务平台中的。
var urlHelper = _urlHelperFactory.GetUrlHelper(_actionContextAccessor.ActionContext);
var isLocalUrl = urlHelper.IsLocalUrl(url);
//如果是相对路径URL字符串,则拼接出相应的绝对路径URL字符串。
var uriStr = url;
if (isLocalUrl)
{
var pathBase = _httpContextAccessor.HttpContext.Request.PathBase;
uriStr = $"{GetStoreHost().TrimEnd('/')}{(url.StartsWith(pathBase) ? url.Replace(pathBase, "") : url)}";
}
//根据拼接出相应的绝对路径URL字符串,实例化1个指定的URI实例。
var uri = new Uri(uriStr, UriKind.Absolute);
//把URL查询键/值字符串,实列化存储到字典实例中。
var queryParameters = QueryHelpers.ParseQuery(uri.Query);
//把1/多个值(每个值之间用“,”分割)存储到字典实例的指定键/值对项中。
queryParameters[key] = string.Join(",", values);
//通过的字典实例,实例化查询生成器实例。
//parameter.Value.FirstOrDefault()?.ToString():如果1个指定键所对应的多个值, 在极端情况下这些值中有相同的,那么只获取第1个(在实际开发中这种场景是不可能出现的)。
var queryBuilder = new QueryBuilder(queryParameters
.ToDictionary(parameter => parameter.Key, parameter => parameter.Value.FirstOrDefault()?.ToString() ?? string.Empty));
//通过查询生成器实例,获取修改更新了查询键/值字符串绝对路径URL字符串(该绝对路径URL字符串即可能是本地的(当前程序的),可能是第3方服务平台中的)。
url = $"{(isLocalUrl ? uri.LocalPath : uri.GetLeftPart(UriPartial.Path))}{queryBuilder.ToQueryString()}{uri.Fragment}";
return url;
}
/// <param name="url">1个指定的URL字符串。</param>
/// <param name="key">1个指定的URL查询键/值字符串中的1个指定键。</param>
/// <param name="value">数该实例存储着1个指定的URL查询键/值字符串中的1个指定键所对应的1个值,默认值:null,即移除URL查询键/值字符串中的1个指定键/值对项。</param>
/// <summary>
/// 【移除查询字符串】
/// <remarks>
/// 摘要:
/// 对1个指定的URL查询键/值字符串中的1个指定键所对应的1/多个值进行修改更新操作(每个值之间用“,”分割)。
/// </remarks>
/// <returns>
/// 返回:
/// 被修改更新了查询键/值字符串后的1个指定的绝对路径URL字符串(该绝对路径URL字符串即可能是本地的(当前程序的),可能是第3方服务平台中的)。
/// </returns>
/// </summary>
public virtual string RemoveQueryString(string url, string key, string value = null)
{
if (string.IsNullOrEmpty(url))
return string.Empty;
if (string.IsNullOrEmpty(key))
return url;
//通过当前程序中的指定控制器的指定行为方法,来验证1个指定的URL字符串,是否是相对路径URL字符串,也可以这样理解相对路径URL字符串一定是本地的(当前程序的),绝对路径URL字符串可能是本地的也可能是第3方服务平台中的。
var urlHelper = _urlHelperFactory.GetUrlHelper(_actionContextAccessor.ActionContext);
var isLocalUrl = urlHelper.IsLocalUrl(url);
//如果是相对路径URL字符串,则拼接出相应的绝对路径URL字符串;或直接获取绝对路径URL字符串。
var uri = new Uri(isLocalUrl ? $"{GetStoreHost().TrimEnd('/')}{url}" : url, UriKind.Absolute);
//把URL查询键/值字符串,实列化存储到字典实例中。
var queryParameters = QueryHelpers.ParseQuery(uri.Query)
.SelectMany(parameter => parameter.Value, (parameter, queryValue) => new KeyValuePair<string, string>(parameter.Key, queryValue))
.ToList();
//如果存在被移除的值,则从字典实例的指定键/值对项中移除该值。
if (!string.IsNullOrEmpty(value))
{
queryParameters.RemoveAll(parameter => parameter.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)
&& parameter.Value.Equals(value, StringComparison.InvariantCultureIgnoreCase));
}
else
{
//如果不存在被移除的值,则从字典实例中移除整个指定键/值对项。
queryParameters.RemoveAll(parameter => parameter.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase));
}
var queryBuilder = new QueryBuilder(queryParameters);
//拼接出被移除了指定查询键/值对项后的的1个指定的绝对路径URL字符串(该绝对路径URL字符串即可能是本地的(当前程序的),可能是第3方服务平台中的)。
url = $"{(isLocalUrl ? uri.LocalPath : uri.GetLeftPart(UriPartial.Path))}{queryBuilder.ToQueryString()}{uri.Fragment}";
return url;
}
/// <typeparam name="T">泛型类型实例(1个指定类的类型实例)。</typeparam>
/// <param name="name">URL查询键/值字符串中的1个指定键。</param>
/// <summary>
/// 【查询字符串】
/// <remarks>
/// 摘要:
/// 该方法以泛型形式把URL查询键/值字符串中字符串类型的1个指定值转换为1个指定类型的1个指定实例。
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定类型的1个指定实例。
/// </returns>
/// </summary>
public virtual T QueryString<T>(string name)
{
if (!IsRequestAvailable())
return default;
//如果URL查询键/值字符串中的1个指定值为空,则根据指定类型实例转换为相应的默认实例,例如:int类型的默认实例为:0。
if (StringValues.IsNullOrEmpty(_httpContextAccessor.HttpContext.Request.Query[name]))
return default;
//把URL查询键/值字符串中字符串类型的1个指定值转换为1个指定类型的1个指定实例。
return CommonHelper.To<T>(_httpContextAccessor.HttpContext.Request.Query[name].ToString());
}
/// <summary>
/// 【重置应用域】
/// <remarks>
/// 摘要:
/// 该方法为客户端浏览器的自动重启操作提供方法支撑。
/// 说明:
/// 例如:在当前程序第1次数据库的初始化安装成功完成后,客户端浏览器就会通过该方法来自动关闭数据库的初始化安装页面,
/// 从而使当程序重新加载所有的初始化数据,保证当前程序能够在客户端浏览器顺序的被运行。
/// </remarks>
/// </summary>
public virtual void RestartAppDomain()
{
_hostApplicationLifetime.StopApplication();
}
/// <summary>
/// 【获取当前请求协议】
/// <remarks>
/// 摘要:
/// 从客户端浏览器中获取对当前程序进行请求访问时所使用的传输协议字符串:"http"/"https"。
/// </remarks>
/// <returns>
/// 返回:
/// 传输协议字符串:"http"/"https"。
/// </returns>
/// </summary>
public virtual string GetCurrentRequestProtocol()
{
return IsCurrentConnectionSecured() ? Uri.UriSchemeHttps : Uri.UriSchemeHttp;
}
/// <param name="request">HTTP请求类的1个指定实例。</param>
/// <summary>
/// 【本地请求?】
/// <remarks>
/// 摘要:
/// 获取1个值false(通过代理)/true(未通过代理),该值指示客户端对当前程序的访问是否通过了代理软件或服务(例如:Nginx等,实际上前后端分离的中的跨域中间件实质上也是一种代理访问)。
/// 说明:
/// 如果是本地请求“LocalIpAddress”与“RemoteIpAddress”是相等的;如果是代理请求“LocalIpAddress”与“RemoteIpAddress”是不等的。
/// </remarks>
/// <returns>
/// 返回:
/// 1个值false(通过代理)/true(未通过代理)。
/// </returns>
/// </summary>
public virtual bool IsLocalRequest(HttpRequest request)
{
//source: https://stackoverflow.com/a/41242493/7860424
var connection = request.HttpContext.Connection;
//如果:远程IP地址实例不符合IPV6规范。
if (IsIpAddressSet(connection.RemoteIpAddress))
{
//如果:本地IP地址实例不符合IPV6规范。
return IsIpAddressSet(connection.LocalIpAddress)
//如果“LocalIpAddress”与“RemoteIpAddress”是相等,则是本地请求,即未通过代理;反之则是代理请求。
? connection.RemoteIpAddress.Equals(connection.LocalIpAddress)
//如果:本地IP地址实例符合IPV6规范,“RemoteIpAddress”符合IPV6本地规范,则是本地请求,即未通过代理;反之则是代理请求。
: IPAddress.IsLoopback(connection.RemoteIpAddress);
}
//IPV6规范实质都是代理请求?
//由于IPV6规范在设计之初都不兼容IPV4规范,为了使IPV6规范兼容IPV4规范,IPV6规范实质都是代理请求(来源于网络不知是否正确)。
return true;
}
/// <param name="request">HTTP请求类的1个指定实例。</param>
/// <summary>
/// 【获取原URL】
/// <remarks>
/// 摘要:
/// 该方法主要为在执行登录操作成功后返回原来的指定页面,提供方法支撑。
/// </remarks>
/// <returns>
/// 返回:
/// 执行登录操作成功后返回原来的指定页面的URL字符串。
/// </returns>
/// </summary>
public virtual string GetRawUrl(HttpRequest request)
{
var rawUrl = request.HttpContext.Features.Get<IHttpRequestFeature>()?.RawTarget;
//or compose raw URL manually
if (string.IsNullOrEmpty(rawUrl))
rawUrl = $"{request.PathBase}{request.Path}{request.QueryString}";
return rawUrl;
}
/// <param name="request">HTTP请求类的1个指定实例。</param>
/// <summary>
/// 【Ajax请求?】
/// <remarks>
/// 摘要:
/// 获取1个值false(不是通过Ajax脚笨命令进行触发的请求)/true(是通过Ajax脚笨命令进行触发的请求),该值指示客户端浏览器请求头字典属性成员实例中是否包含有Ajax请求值,如果有则客户端的请求是通过Ajax脚笨命令进行触发的请求。
/// </remarks>
/// <returns>
/// 返回:
/// 1个值false(不是通过Ajax脚笨命令进行触发的请求)/true(是通过Ajax脚笨命令进行触发的请求)。
/// </returns>
/// </summary>
public virtual bool IsAjaxRequest(HttpRequest request)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
if (request.Headers == null)
return false;
return request.Headers["X-Requested-With"] == "XMLHttpRequest";
}
#endregion
}
}
5 重构Framework.Infrastructure.DependencyInjectionStartup. ConfigureServices
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddScoped<INopFileProvider, NopFileProvider>();
services.AddScoped<IWebHelper, WebHelper>();
6 WebApi.Controllers.CustomerController. GetAvatarUrl
/// <param name="customerId">1个指定的长整型值。</param>
/// <summary>
/// 【头像图片绝对URL--无需权限】
/// </summary>
/// <remarks>
/// 摘要:
/// 获取1个指定用户头像图片的绝对URL字符串,为前端头像图片的渲染显示提供数据支撑。
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定用户头像图片的绝对URL字符串,为前端头像图片的渲染显示提供数据支撑。
/// </returns>
[HttpGet]
public async Task<MessageModel<string>> GetAvatarUrl(long customerId)
{
string _avatarUrl = string.Empty;
Customer _customer = await _customerService.GetCustomerByIdAsync(customerId);
if (_customer!=null && string.IsNullOrEmpty(_customer.Avatar))
{
string _path = _nopFileProvider.Combine(_nopFileProvider.WebRootPath, @"\images\Avatar\Default.jpg");
if (_nopFileProvider.FileExists(_path))
{
string _absoluteAvatarUrl = _nopFileProvider.GetVirtualPath(@"\images\Avatar\Default.jpg");
//去URL格式路径字符中的第一个字符:“~/”。
_absoluteAvatarUrl = _absoluteAvatarUrl.Replace("~/", string.Empty);
_avatarUrl = _webHelper.GetStoreHost() + _absoluteAvatarUrl;
}
}
if(_customer != null && !string.IsNullOrEmpty(_customer.Avatar))
{
_avatarUrl = _webHelper.GetStoreHost().TrimEnd('/') + _customer.Avatar;
}
if(_customer == null || string.IsNullOrEmpty(_avatarUrl))
return MessageModel<string>.Fail("获取指定用户头像图片的绝对URL字符串失败!", 500);
return MessageModel<string>.GetSuccess("成功获取指定用户头像图片的绝对URL字符串!", _avatarUrl);
}
对以上功能更为具体实现和注释见:230309_044shopDemo(图片URL的后端获取)。