亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

ASP.NET?Core?WebApi返回結果統一包裝的方法

發布時間:2022-04-11 13:42:05 來源:億速云 閱讀:183 作者:iii 欄目:開發技術

這篇“ASP.NET Core WebApi返回結果統一包裝的方法”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“ASP.NET Core WebApi返回結果統一包裝的方法”文章吧。

統一結果類封裝

首先如果讓返回的結果格式統一,就得有一個統一的包裝類去包裝所有的返回結果,因為返回的具體數據雖然格式一致,但是具體的值的類型是不確定的,因此我們這里需要定義個泛型類。當然如果你不選擇泛型類的話用dynamic或者object類型也是可以的,但是這樣的話可能會帶來兩點不足

  • 一是可能會存在裝箱拆箱的操作。

  • 二是如果引入swagger的話是沒辦法生成返回的類型的,因為dynamic或object類型都是執行具體的action時才能確定返回類型的,但是swagger的結構是首次運行的時候就獲取到的,因此無法感知具體類型。

定義包裝類

上面我們也說了關于定義泛型類的優勢,這里就話不多說來直接封裝一個結果返回的包裝類

public class ResponseResult<T>
{
    /// <summary>
    /// 狀態結果
    /// </summary>
    public ResultStatus Status { get; set; } = ResultStatus.Success;

    private string? _msg;

    /// <summary>
    /// 消息描述
    /// </summary>
    public string? Message
    {
        get
        {
            // 如果沒有自定義的結果描述,則可以獲取當前狀態的描述
            return !string.IsNullOrEmpty(_msg) ? _msg : EnumHelper.GetDescription(Status);
        }
        set
        {
            _msg = value;
        }
    }

    /// <summary>
    /// 返回結果
    /// </summary>
    public T Data { get; set; }
}

其中這里的ResultStatus是一個枚舉類型,用于定義具體的返回狀態碼,用于判斷返回的結果是正常還是異常或者其他,我這里只是簡單的定義了一個最簡單的示例,有需要的話也可以自行擴展

public enum ResultStatus
{
    [Description("請求成功")]
    Success = 1,
    [Description("請求失敗")]
    Fail = 0,
    [Description("請求異常")]
    Error = -1
}

這種情況下定義枚舉類型并且結合它的DescriptionAttribute的特性去描述枚舉的含義是一個不錯的選擇,首先它可以統一管理每個狀態的含義,其次是更方便的獲取每個狀態對應的描述。這樣的話如果沒有自定義的結果描述,則可以獲取當前狀態的描述來充當默認值的情況。這個時候在寫具體action的時候會是以下的效果

[HttpGet("GetWeatherForecast")]
public ResponseResult<IEnumerable<WeatherForecast>> GetAll()
{
    var datas = Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        Date = DateTime.Now.AddDays(index),
        TemperatureC = Random.Shared.Next(-20, 55),
        Summary = Summaries[Random.Shared.Next(Summaries.Length)]
    });
    return new ResponseResult<IEnumerable<WeatherForecast>> {  Data = datas };
}

這樣的話每次編寫action的時候都可以返回一個ResponseResult<T>的結果了,這里就體現出了使用枚舉定義狀態碼的優勢了,相當一部分場景我們可以省略了狀態碼甚至是消息的編寫,畢竟很多時候在保障功能的情況下,代碼還是越簡介越好的,更何況是一些高頻操作呢。

升級一下操作

上面雖然我們定義了ResponseResult<T>來統一包裝返回結果,但是每次還得new一下,在無疑是不太方便的,而且還要每次都還得給屬性賦值啥的,也是夠麻煩的,這個時候就想,如果能有相關的輔助方法去簡化操作就好了,方法不用太多能滿足場景就好,也就是夠用就好,最主要的是能支持擴展就可以。因此,進一步升級一下結果包裝類,來簡化一下操作

public class ResponseResult<T>
{
    /// <summary>
    /// 狀態結果
    /// </summary>
    public ResultStatus Status { get; set; } = ResultStatus.Success;

    private string? _msg;

    /// <summary>
    /// 消息描述
    /// </summary>
    public string? Message
    {
        get
        {
            return !string.IsNullOrEmpty(_msg) ? _msg : EnumHelper.GetDescription(Status);
        }
        set
        {
            _msg = value;
        }
    }

    /// <summary>
    /// 返回結果
    /// </summary>
    public T Data { get; set; }

    /// <summary>
    /// 成功狀態返回結果
    /// </summary>
    /// <param name="result">返回的數據</param>
    /// <returns></returns>
    public static ResponseResult<T> SuccessResult(T data)
    {
        return new ResponseResult<T> { Status = ResultStatus.Success, Data = data };
    }

    /// <summary>
    /// 失敗狀態返回結果
    /// </summary>
    /// <param name="code">狀態碼</param>
    /// <param name="msg">失敗信息</param>
    /// <returns></returns>
    public static ResponseResult<T> FailResult(string? msg = null)
    {
        return new ResponseResult<T> { Status = ResultStatus.Fail, Message = msg };
    }

    /// <summary>
    /// 異常狀態返回結果
    /// </summary>
    /// <param name="code">狀態碼</param>
    /// <param name="msg">異常信息</param>
    /// <returns></returns>
    public static ResponseResult<T> ErrorResult(string? msg = null)
    {
        return new ResponseResult<T> { Status = ResultStatus.Error, Message = msg };
    }

    /// <summary>
    /// 自定義狀態返回結果
    /// </summary>
    /// <param name="status"></param>
    /// <param name="result"></param>
    /// <returns></returns>
    public static ResponseResult<T> Result(ResultStatus status, T data, string? msg = null)
    {
        return new ResponseResult<T> { Status = status, Data = data, Message = msg };
    }
}

這里進一步封裝了幾個方法,至于具體封裝幾個這種方法,還是那句話夠用就好,這里我封裝了幾個常用的操作,成功狀態、失敗狀態、異常狀態、最完全狀態,這幾種狀態基本上可以滿足大多數的場景,不夠的話可以自行進行進一步的多封裝幾個方法。這樣的話在action使用的時候就會簡化很多,省去了手動屬性賦值

[HttpGet("GetWeatherForecast")]
public ResponseResult<IEnumerable<WeatherForecast>> GetAll()
{
    var datas = Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        Date = DateTime.Now.AddDays(index),
        TemperatureC = Random.Shared.Next(-20, 55),
        Summary = Summaries[Random.Shared.Next(Summaries.Length)]
    });
    return ResponseResult<IEnumerable<WeatherForecast>>.SuccessResult(datas);
}

進一步完善

上面我們通過完善ResponseResult<T>類的封裝,確實在某些程度上節省了一部分操作,但是還是有點美中不足,那就是每次返回結果的時候,雖然定義了幾個常用的靜態方法去操作返回結果,但是每次還得通過手動去把ResponseResult<T>類給請出來才能使用,現在呢想在操作返回結果的時候不想看到它了。這個呢也很簡單,我們可以借助微軟針對MVC的Controller的封裝進一步得到一個思路,那就是定義一個基類的Controller,我們在Controller基類中,把常用的返回結果封裝一些方法,這樣在Controller的子類中呢就可以直接調用這些方法,而不需要關注如何去編寫方法的返回類型了,話不多說動手把Controller基類封裝起來

[ApiController]
[Route("api/[controller]")]
public class ApiControllerBase : ControllerBase
{
    /// <summary>
    /// 成功狀態返回結果
    /// </summary>
    /// <param name="result">返回的數據</param>
    /// <returns></returns>
    protected ResponseResult<T> SuccessResult<T>(T result)
    {
        return ResponseResult<T>.SuccessResult(result);
    }

    /// <summary>
    /// 失敗狀態返回結果
    /// </summary>
    /// <param name="code">狀態碼</param>
    /// <param name="msg">失敗信息</param>
    /// <returns></returns>
    protected ResponseResult<T> FailResult<T>(string? msg = null)
    {
        return ResponseResult<T>.FailResult(msg);
    }

    /// <summary>
    /// 異常狀態返回結果
    /// </summary>
    /// <param name="code">狀態碼</param>
    /// <param name="msg">異常信息</param>
    /// <returns></returns>
    protected ResponseResult<T> ErrorResult<T>(string? msg = null)
    {
        return ResponseResult<T>.ErrorResult(msg);
    }

    /// <summary>
    /// 自定義狀態返回結果
    /// </summary>
    /// <param name="status"></param>
    /// <param name="result"></param>
    /// <returns></returns>
    protected ResponseResult<T> Result<T>(ResultStatus status, T result, string? msg = null)
    {
        return ResponseResult<T>.Result(status, result, msg);
    }
}

有了這么一個大神的輔助,一切似乎又向著美好進發了一步,這樣的話每次我們自定義的Controller可以繼承ApiControllerBase類,從而使用里面的簡化操作。所以再寫起來代碼,大概是這么一個效果

public class WeatherForecastController : ApiControllerBase
{
    private static readonly string[] Summaries = new[]
    {
       "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    [HttpGet("GetWeatherForecast")]
    public ResponseResult<IEnumerable<WeatherForecast>> GetAll()
    {
        var datas = Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
        });
        return SuccessResult(datas);
    }
}

這個時候確實變得很美好了,但是還是沒有逃脫一點,那就是我還是得通過特定的方法來得到一個ResponseResult<T>類型的返回結果,包括我們給ResponseResult<T>類封裝靜態方法,或者甚至是定義ApiControllerBase基類,都是為了進一步簡化這個操作。現在呢我想告別這個限制,我能不能把返回的結果直接就默認的轉化成ResponseResult<T>類型的結果呢?當然可以,這也是通過ASP.NET Core的封裝思路中得到的啟發,借助implicit自動完成隱式轉換,這個在ASP.NET Core的ActionResult<T>類中也有體現

public static implicit operator ActionResult<TValue>(TValue value)
{
    return new ActionResult<TValue>(value);
}

通過這個思路我們可以進一步完善ResponseResult<T>類的實現方式,給它添加一個隱式轉換的操作,僅僅定義一個方法即可,在ResponseResult<T>類中繼續完善

/// <summary>
/// 隱式將T轉化為ResponseResult<T>
/// </summary>
/// <param name="value"></param>
public static implicit operator ResponseResult<T>(T value)
{
    return new ResponseResult<T> { Data = value };
}

這種對于絕大部分返回成功結果的時候提供了非常簡化的操作,這個時候如果你再去使用action的時候就可以進一步來簡化返回值的操作了

[HttpGet("GetWeatherForecast")]
public ResponseResult<IEnumerable<WeatherForecast>> GetAll()
{
    var datas = Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        Date = DateTime.Now.AddDays(index),
        TemperatureC = Random.Shared.Next(-20, 55),
        Summary = Summaries[Random.Shared.Next(Summaries.Length)]
    });
    return datas.ToList();
}

因為我們定義了TResponseResult<T>的隱式轉換,所以這個時候我們就可以直接返回結果了,而不需要手動對結果返回值進行包裝。

漏網之魚處理

在上面我們為了盡量簡化action返回ResponseResult<T>的統一返回結構的封裝,已經對ResponseResult<T>類進行了許多的封裝,并且還通過封裝ApiControllerBase基類進一步簡化這一操作,但是終究還是避免不了一點,那就是很多時候可能想不起來對action的返回值去加ResponseResult<T>類型的返回值,但是我們之前的所有封裝都得建立在必須要聲明ResponseResult<T>類型的返回值的基礎上才行,否則就不存在統一返回格式這一說法了。所以針對這些漏網之魚,我們必須要有統一的攔截機制,這樣才能更完整的針對返回結果進行處理,針對這種對action返回值的操作,我們首先想到的就是定義過濾器進行處理,因此筆者針對這一現象封裝了一個統一包裝結果的過濾器,實現如下

public class ResultWrapperFilter : ActionFilterAttribute
{
    public override void OnResultExecuting(ResultExecutingContext context)
    {
        var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
        var actionWrapper = controllerActionDescriptor?.MethodInfo.GetCustomAttributes(typeof(NoWrapperAttribute), false).FirstOrDefault();
        var controllerWrapper = controllerActionDescriptor?.ControllerTypeInfo.GetCustomAttributes(typeof(NoWrapperAttribute), false).FirstOrDefault();
        //如果包含NoWrapperAttribute則說明不需要對返回結果進行包裝,直接返回原始值
        if (actionWrapper != null || controllerWrapper != null)
        {
            return;
        }

        //根據實際需求進行具體實現
        var rspResult = new ResponseResult<object>();
        if (context.Result is ObjectResult)
        {
            var objectResult = context.Result as ObjectResult;
            if (objectResult?.Value == null)
            {
                rspResult.Status = ResultStatus.Fail;
                rspResult.Message = "未找到資源";
                context.Result = new ObjectResult(rspResult);
            }
            else
            {
                //如果返回結果已經是ResponseResult<T>類型的則不需要進行再次包裝了
                if (objectResult.DeclaredType.IsGenericType && objectResult.DeclaredType?.GetGenericTypeDefinition() == typeof(ResponseResult<>))
                {
                    return;
                }
                rspResult.Data = objectResult.Value;
                context.Result = new ObjectResult(rspResult);
            }
            return;
        }
    }
}

在使用WebAPI的過程中,我們的action絕大部分是直接返回ViewModelDto而并沒有返回ActionResult類型相關,但是無妨,這個時候MVC的底層操作會為我們將這些自定義的類型包裝成ObjectResult類型的,因此我們的ResultWrapperFilter過濾器也是通過這一機制進行操作的。這里有兩點需要考慮的

  • 首先是,我們必須要允許并非所有的返回結果都要進行ResponseResult<T>的包裝,為了滿足這一需求我們還定義了NoWrapperAttribute來實現這一效果,只要Controller或Action有NoWrapperAttribute的修飾則不對返回結果進行任何處理。

  • 其次是,如果我們的Action上的返回類型已經是ResponseResult<T>類型的,則也不需要對返回結果進行再次的包裝。

關于ResultWrapperFilter的定義其實很簡單,因為在這里它只是起到了一個標記的作用

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class NoWrapperAttribute:Attribute
{
}

到了這里,還有一種特殊的情況需要注意,那就是當程序發生異常的時候,我們上面的這些機制也是沒有辦法生效的,因此我們還需要定義一個針對全局異常處理的攔截機制,同樣是可以使用統一異常處理過濾器進行操作,實現如下

public class GlobalExceptionFilter : IExceptionFilter
{
    private readonly ILogger<GlobalExceptionFilter> _logger;
    public GlobalExceptionFilter(ILogger<GlobalExceptionFilter> logger)
    {
        _logger = logger;
    }

    public void OnException(ExceptionContext context)
    {
        //異常返回結果包裝
        var rspResult = ResponseResult<object>.ErrorResult(context.Exception.Message);
        //日志記錄
        _logger.LogError(context.Exception, context.Exception.Message);
        context.ExceptionHandled = true;
        context.Result = new InternalServerErrorObjectResult(rspResult);
    }

    public class InternalServerErrorObjectResult : ObjectResult
    {
        public InternalServerErrorObjectResult(object value) : base(value)
        {
            StatusCode = StatusCodes.Status500InternalServerError;
        }
    }
}

寫完過濾器了,千萬不能忘了全局注冊一下,否則它也就只能看看了,不會起到任何效果

builder.Services.AddControllers(options =>
{
    options.Filters.Add<ResultWrapperFilter>();
    options.Filters.Add<GlobalExceptionFilter>();
});

漏網之魚另一種處理

當然針對上面兩種針對漏網之魚的處理,在ASP.NET Core上還可以通過中間件的方式進行處理,至于過濾器和中間件有何不同,相信大家已經非常清楚了,核心不同總結起來就一句話二者的處理階段不同,即針對管道的生命周期處理是不一樣的,中間件可以處理任何生命周期在它之后的場景,但是過濾器只管理Controller這一塊的一畝三分地但是針對結果包裝這一場景,筆者覺得使用過濾器的方式更容易處理一點,因為畢竟我們是要操作Action的返回結果,通過過濾器中我們可以直接拿到返回結果的值。但是這個操作如果在中間件里進行操作的話,只能通過讀取Response.Body進行操作了,筆者這里也封裝了一個操作,如下所示

public static IApplicationBuilder UseResultWrapper(this IApplicationBuilder app)
{
        var serializerOptions = app.ApplicationServices.GetRequiredService<IOptions<JsonOptions>>().Value.JsonSerializerOptions;
        serializerOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping;
        return app.Use(async (context, next) =>
        {
            var originalResponseBody = context.Response.Body;
            try
            {
                //因為Response.Body沒辦法進行直接讀取,所以需要特殊操作一下
                using var swapStream = new MemoryStream();
                context.Response.Body = swapStream;
                await next();
                //判斷是否出現了異常狀態碼,需要特殊處理
                if (context.Response.StatusCode == StatusCodes.Status500InternalServerError)
                {
                    context.Response.Body.Seek(0, SeekOrigin.Begin);
                    await swapStream.CopyToAsync(originalResponseBody);
                    return;
                }
                var endpoint = context.Features.Get<IEndpointFeature>()?.Endpoint;
                if (endpoint != null)
                {
                    //只針對application/json結果進行處理
                    if (context.Response.ContentType.ToLower().Contains("application/json"))
                    {
                        //判斷終結點是否包含NoWrapperAttribute
                        NoWrapperAttribute noWrapper = endpoint.Metadata.GetMetadata<NoWrapperAttribute>();
                        if (noWrapper != null)
                        {
                            context.Response.Body.Seek(0, SeekOrigin.Begin);
                            await swapStream.CopyToAsync(originalResponseBody);
                            return;
                        }
                        //獲取Action的返回類型
                        var controllerActionDescriptor = context.GetEndpoint()?.Metadata.GetMetadata<ControllerActionDescriptor>();
                        if (controllerActionDescriptor != null)
                        {
                            //泛型的特殊處理
                            var returnType = controllerActionDescriptor.MethodInfo.ReturnType;
                            if (returnType.IsGenericType && (returnType.GetGenericTypeDefinition() == typeof(Task<>) || returnType.GetGenericTypeDefinition() == typeof(ValueTask<>)))
                            {
                                returnType = returnType.GetGenericArguments()[0];
                            }
                            //如果終結點已經是ResponseResult<T>則不進行包裝處理
                            if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(ResponseResult<>))
                            {
                                context.Response.Body.Seek(0, SeekOrigin.Begin);
                                await swapStream.CopyToAsync(originalResponseBody);
                                return;
                            }
                            context.Response.Body.Seek(0, SeekOrigin.Begin);
                            //反序列化得到原始結果
                            var result = await JsonSerializer.DeserializeAsync(context.Response.Body, returnType, serializerOptions);
                            //對原始結果進行包裝
                            var bytes = JsonSerializer.SerializeToUtf8Bytes(ResponseResult<object>.SuccessResult(result), serializerOptions);
                            new MemoryStream(bytes).CopyTo(originalResponseBody);
                            return;
                        }
                    }
                }
                context.Response.Body.Seek(0, SeekOrigin.Begin);
                await swapStream.CopyToAsync(originalResponseBody);
            }
            finally
            {
                //將原始的Body歸還回來
                context.Response.Body = originalResponseBody;
            }
        });
    }
}

相信通過上面的處理,我們就可以更容易的看出來,誰更容易的對統一結果進行包裝處理了,畢竟我們是針對Action的返回結果進行處理,而過濾器顯然就是為針對Controller和Action的處理而生的。但是通過中間件的方式能更完整的針對結果進行處理,因為許多時候我們可能是在自定義的中間件里直接攔截請求并返回,但是根據二八原則這種情況相對于Action的返回值畢竟是少數,有這種情況我們可以通過直接ResponseResult<T>封裝的方法進行返回操作,也很方便。但是這個時候呢,關于異常處理我們通過全局異常處理中間件,則能更多的處理更多的場景,且沒有副作用,看一下它的定義

public static IApplicationBuilder UseException(this IApplicationBuilder app)
{
    return app.UseExceptionHandler(configure =>
    {
        configure.Run(async context =>
        {
            var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();
            var ex = exceptionHandlerPathFeature?.Error;
            if (ex != null)
            {
                var _logger = context.RequestServices.GetService<ILogger<IExceptionHandlerPathFeature>>();
                var rspResult = ResponseResult<object>.ErrorResult(ex.Message);
                _logger?.LogError(ex, message: ex.Message);
                context.Response.StatusCode = StatusCodes.Status500InternalServerError;
                context.Response.ContentType = "application/json;charset=utf-8";
                await context.Response.WriteAsync(rspResult.SerializeObject());
            }
        });
    });
}

使用全局異常梳理中間件是沒有副作用的,主要因為在異常處理的時候我們不需要讀取Response.Body進行讀取操作的,所以至于是選擇異常處理中間件還是過濾器,大家可以針對自己的實際場景進行選擇,兩種方式都是可以的。

以上就是關于“ASP.NET Core WebApi返回結果統一包裝的方法”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

微博| 夏津县| 永修县| 锡林浩特市| 宝山区| 乌审旗| 青龙| 剑川县| 苍溪县| 涟源市| 且末县| 通海县| 罗山县| 博白县| 大埔区| 老河口市| 玛纳斯县| 福鼎市| 瑞金市| 开平市| 安达市| 金华市| 肥城市| 景东| 涞源县| 荥经县| 游戏| 都江堰市| 寿光市| 桦南县| 绍兴市| 合肥市| 固安县| 马龙县| 佛坪县| 凤冈县| 诸暨市| 凌海市| 江山市| 惠州市| 汝南县|