Blog project based on abp vNext and.NET Core - Unified Specification API, Packaging Return Model

Keywords: github

Last article ( https://www.cnblogs.com/meowv/p/12916613.html ) A simple case of adding or deleting changes was completed using the custom warehouse. The interested students can see that our return parameters are confused and unfriendly.

In the actual development process, each company may be different, but they are very different. Our return data is wrapped under a common model instead of returning the final data directly. In the return parameters, it shows the timestamp of the current request, whether the request was successful or not, and what is the error message, the status code (the status code can be our own).Defined values) and so on.It may seem tedious and unnecessary, but there is no doubt that the benefits of doing so are not only to beautify our API s, but also to facilitate the data processing of front-end students.

We put a unified return model in the.ToolKits layer, which was previously said to be primarily a public tool class, an extension method.

Create a new Base folder and add the response entity class ServiceResult.cs, define a separate ServiceResultCode response code enumeration under the Enum folder, 0/1.Represents success and failure, respectively.

//ServiceResultCode.cs
namespace Meowv.Blog.ToolKits.Base.Enum
{
    /// <summary>
    ///Service Layer Response Code Enumeration
    /// </summary>
    public enum ServiceResultCode
    {
        /// <summary>
        ///Success
        /// </summary>
        Succeed = 0,

        /// <summary>
        ///Failure
        /// </summary>
        Failed = 1,
    }
}
//ServiceResult.cs
using Meowv.Blog.ToolKits.Base.Enum;
using System;

namespace Meowv.Blog.ToolKits.Base
{
    /// <summary>
    ///Service Layer Response Entities
    /// </summary>
    public class ServiceResult
    {
        /// <summary>
        ///Response Code
        /// </summary>
        public ServiceResultCode Code { get; set; }

        /// <summary>
        ///Response Information
        /// </summary>
        public string Message { get; set; }

        /// <summary>
        ///Success
        /// </summary>
        public bool Success => Code == ServiceResultCode.Succeed;

        /// <summary>
        ///timestamp (milliseconds)
        /// </summary>
        public long Timestamp { get; } = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();

        /// <summary>
        ///Response succeeded
        /// </summary>
        /// <param name="message"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        public void IsSuccess(string message = "")
        {
            Message = message;
            Code = ServiceResultCode.Succeed;
        }

        /// <summary>
        ///Failed to respond
        /// </summary>
        /// <param name="message"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        public void IsFailed(string message = "")
        {
            Message = message;
            Code = ServiceResultCode.Failed;
        }

        /// <summary>
        ///Failed to respond
        /// </summary>
        /// <param name="exexception></param>
        /// <param name="data"></param>
        /// <returns></returns>
        public void IsFailed(Exception exception)
        {
            Message = exception.InnerException?.StackTrace;
            Code = ServiceResultCode.Failed;
        }
    }
}

As you can see, a Message of string type, a Success of bool type, and a Success depending on Code ==ServiceResultCode.SucceedResults.There is also a current timestamp, Timestamp.

There are also IsSuccess(...) and IsFailed(...) methods, which are not difficult to understand when we successfully return data or when a system error or parameter exception occurs.

For the time being, this return model only supports api usage that does not require return parameters, and needs to be extended to allow us to return other complex types of data.So you also need to add a return model that supports generics, creating a new model class:ServiceResultOfT.cs, where T is our return result, then inherits our ServiceResult and specifies T as class.And rewrite an IsSuccess(...) method with the following code:

//ServiceResultOfT.cs
using Meowv.Blog.ToolKits.Base.Enum;

namespace Meowv.Blog.ToolKits.Base
{
    /// <summary>
    ///Service Layer Response Entities (Generic)
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class ServiceResult<T> : ServiceResult where T : class
    {
        /// <summary>
        ///Return results
        /// </summary>
        public T Result { get; set; }

        /// <summary>
        ///Response succeeded
        /// </summary>
        /// <param name="result"></param>
        /// <param name="message"></param>
        public void IsSuccess(T result = null, string message = "")
        {
            Message = message;
            Code = ServiceResultCode.Succeed;
            Result = result;
        }
    }
}

At this point, the requirements can be met for APIs that do not need and need to return parameters.But there's another way we can't do that: with paginated data, we all know that if we want to paginate, the total number of data must be necessary.

So you also need to extend a paging response entity at this point, so when we use it, we can satisfy this requirement by directly using the paging response entity as the T parameter in the ServiceResult <T> above.

New folder Paged, add total interface IHasTotalCount, return result list interface IListResult

//IHasTotalCount.cs
namespace Meowv.Blog.ToolKits.Base.Paged
{
    public interface IHasTotalCount
    {
        /// <summary>
        ///Total
        /// </summary>
        int Total { get; set; }
    }
}

//IListResult.cs
using System.Collections.Generic;

namespace Meowv.Blog.ToolKits.Base.Paged
{
    public interface IListResult<T>
    {
        /// <summary>
        ///Return results
        /// </summary>
        IReadOnlyList<T> Item { get; set; }
    }
}

IListResult<T>accepts a parameter and specifies it to be returned as IReadOnlyList.

Now implement the IListResult interface, create a new ListResult implementation class, inherit IListResult, and assign it to the constructor with the following code:

//ListResult.cs
using System.Collections.Generic;

namespace Meowv.Blog.ToolKits.Base.Paged
{
    public class ListResult<T> : IListResult<T>
    {
        IReadOnlyList<T> item;

        public IReadOnlyList<T> Item
        {
            get => item ?? (item = new List<T>());
            set => item = value;
        }

        public ListResult()
        {
        }

        public ListResult(IReadOnlyList<T> item)
        {
            Item = item;
        }
    }
}

Finally, create our new Paging Response Entity Interface: IPagedList and Paging Response Entity Implementation Class: PagedList, which also accepts a generic parameter T.

The interface inherits IListResult<T> and IHasTotalCount, and the implementation class inherits ListResult<T> and IPagedList<T> and assigns values to them in the constructor.The code is as follows:

//IPagedList.cs
namespace Meowv.Blog.ToolKits.Base.Paged
{
    public interface IPagedList<T> : IListResult<T>, IHasTotalCount
    {
    }
}

//PagedList.cs
using Meowv.Blog.ToolKits.Base.Paged;
using System.Collections.Generic;

namespace Meowv.Blog.ToolKits.Base
{
    /// <summary>
    ///Paging response entity
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class PagedList<T> : ListResult<T>, IPagedList<T>
    {
        /// <summary>
        ///Total
        /// </summary>
        public int Total { get; set; }

        public PagedList()
        {
        }

        public PagedList(int total, IReadOnlyList<T> result) : base(result)
        {
            Total = total;
        }
    }
}

Here our return model is complete. Take a look at our project level catalog at this time.

Next, let's practice modifying the return parameters of the add-delete check interface we created earlier.

//IBlogService.cs
using Meowv.Blog.Application.Contracts.Blog;
using Meowv.Blog.ToolKits.Base;
using System.Threading.Tasks;

namespace Meowv.Blog.Application.Blog
{
    public interface IBlogService
    {
        //Task<bool> InsertPostAsync(PostDto dto);
        Task<ServiceResult<string>> InsertPostAsync(PostDto dto);

        //Task<bool> DeletePostAsync(int id);
        Task<ServiceResult> DeletePostAsync(int id);

        //Task<bool> UpdatePostAsync(int id, PostDto dto);
        Task<ServiceResult<string>> UpdatePostAsync(int id, PostDto dto);

        //Task<PostDto> GetPostAsync(int id);
        Task<ServiceResult<PostDto>> GetPostAsync(int id);
    }
}

Interfaces are all asynchronous, using ServiceResult package as return model, adding and updating T parameters as string type, deleting without returning results directly, and then querying for ServiceResult <PostDto>, take a look at the implementation class:

//BlogService.cs
...
        public async Task<ServiceResult<string>> InsertPostAsync(PostDto dto)
        {
            var result = new ServiceResult<string>();

            var entity = new Post
            {
                Title = dto.Title,
                Author = dto.Author,
                Url = dto.Url,
                Html = dto.Html,
                Markdown = dto.Markdown,
                CategoryId = dto.CategoryId,
                CreationTime = dto.CreationTime
            };

            var post = await _postRepository.InsertAsync(entity);
            if (post == null)
            {
                result.IsFailed("Failed to add");
                return result;
            }

            result.IsSuccess("Added Successfully");
            return result;
        }

        public async Task<ServiceResult> DeletePostAsync(int id)
        {
            var result = new ServiceResult();

            await _postRepository.DeleteAsync(id);

            return result;
        }

        public async Task<ServiceResult<string>> UpdatePostAsync(int id, PostDto dto)
        {
            var result = new ServiceResult<string>();

            var post = await _postRepository.GetAsync(id);
            if (post == null)
            {
                result.IsFailed("Article does not exist");
                return result;
            }

            post.Title = dto.Title;
            post.Author = dto.Author;
            post.Url = dto.Url;
            post.Html = dto.Html;
            post.Markdown = dto.Markdown;
            post.CategoryId = dto.CategoryId;
            post.CreationTime = dto.CreationTime;

            await _postRepository.UpdateAsync(post);


            result.IsSuccess("Update Successful");
            return result;
        }

        public async Task<ServiceResult<PostDto>> GetPostAsync(int id)
        {
            var result = new ServiceResult<PostDto>();

            var post = await _postRepository.GetAsync(id);
            if (post == null)
            {
                result.IsFailed("Article does not exist");
                return result;
            }

            var dto = new PostDto
            {
                Title = post.Title,
                Author = post.Author,
                Url = post.Url,
                Html = post.Html,
                Markdown = post.Markdown,
                CategoryId = post.CategoryId,
                CreationTime = post.CreationTime
            };

            result.IsSuccess(dto);
            return result;
        }
...

When successful, call the IsSuccess(...) method, and when failed, call the IsFailed(...) method.Ultimately, we return either a new ServiceResult() or a new ServiceResult <T>() object.

Also, don't forget to modify it in Controller as follows:

//BlogController.cs
...
        ...
        public async Task<ServiceResult<string>> InsertPostAsync([FromBody] PostDto dto)
        ...

        ...
        public async Task<ServiceResult> DeletePostAsync([Required] int id)
        ...

        ...
        public async Task<ServiceResult<string>> UpdatePostAsync([Required] int id, [FromBody] PostDto dto)
        ...

        ...
        public async Task<ServiceResult<PostDto>> GetPostAsync([Required] int id)
        ...
...

Now go to our Swagger document and make a request. Here we call the query interface to see how it looks and how it works.

This is a fairly simple story, mainly about wrapping our api to make the results more formal.So, have you learned?_

Open source address: https://github.com/Meowv/Blog/tree/blog_tutorial

Posted by idealbrain on Fri, 22 May 2020 18:03:55 -0700