從頭開始一步一步實現EF6+Autofac+MVC5+Bootstarp極簡的實現前後臺ajax表格展示及分頁實現

来源:http://www.cnblogs.com/yuzeyong/archive/2016/04/19/5406638.html
-Advertisement-
Play Games

本來是想試著做一個簡單OA項目玩玩的,真是不做不知道,一做嚇死人,原來以為很簡單的事情,但是做起來不是忘這就是忘那的,有的技術還得重新溫習。所以還是得記錄。免得哪天電腦掛了,就全沒有了。 開始是看了園子里何鎮汐的一系列文章,寫的太好了,只看了幾篇就有想寫代碼的衝動,很大一部分都是搬他的東西。但是我還 ...


本來是想試著做一個簡單OA項目玩玩的,真是不做不知道,一做嚇死人,原來以為很簡單的事情,但是做起來不是忘這就是忘那的,有的技術還得重新溫習。所以還是得記錄。免得哪天電腦掛了,就全沒有了。

開始是看了園子里何鎮汐的一系列文章,寫的太好了,只看了幾篇就有想寫代碼的衝動,很大一部分都是搬他的東西。但是我還是領誤不了DDD,所以先就著三層搞搞。

我搞了兩個解決方案,一個本著是想做框架,把有通用的封裝了,以後要用就引dll,由於太枯燥,另一個就是想做個玩具項目,兩邊輪流搞搞

先是dll部分的,當然很多都沒實現反正是自己的,以後再慢慢補,我這裡東西很少,所以沒用文件夾之類的,全部直接一個命名空間

先是autofac封裝類,只是簡單封裝了一下,做玩具夠用,跟上一篇博客一樣的,這裡不說了

using Autofac;
using System;

namespace MTF.Domain
{
    public class DependencyContainer
    {
        public static IContainer Container { get { return container; } }
        public static IContainer container;
        private DependencyContainer() { }
        public static void Initializer(Action<ContainerBuilder> action)
        {
            ContainerBuilder builder = new ContainerBuilder();
            if (action != null)
            {
                action(builder);
                container = builder.Build();
            }
        }
        private static void Error()
        {
            if (container == null)
                throw new InvalidOperationException("容器沒有初始化");
            else
                return;
        }
        public static TService Resolve<TService>()
        {
            return container.Resolve<TService>();
        }
    }
}
Dependency類代碼

 

BaseEntity 的設計是參考何兄的,大家可以去看他的文章,寫的太好了,這裡幾乎是搬的

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace MTF.Domain
{
    public abstract class BaseEntity<TKey> : IEntity<TKey>
    {
        #region 初始化
        public TKey GId { get; private set; }
        protected BaseEntity(TKey id)
        {
            this.GId = id;
            validationRules = new List<IValidationRule>();
        } 
        #endregion

        #region 實體相等性比較
        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;
            if (!(obj is BaseEntity<TKey>))
                return false;
            return this == (BaseEntity<TKey>)obj;
        }
        public override int GetHashCode()
        {
            return this.GId.GetHashCode();
        }
        public static bool operator ==(BaseEntity<TKey> entity1, BaseEntity<TKey> entity2)
        {
            if ((object)entity1 == null || (object)entity2 == null)
                return false;
            if (entity1.GId == null)
                return false;
            if (entity1.GId.Equals(default(TKey)))
                return false;
            return entity1.GId.Equals(entity2.GId);
        }
        public static bool operator !=(BaseEntity<TKey> entity1, BaseEntity<TKey> entity2)
        {
            return !(entity1 == entity2);
        }
        #endregion

        #region 驗證、驗證規則添加及驗證處理
        private List<IValidationRule> validationRules;//驗證結果集合
        private ICollection<ValidationResult> validationResults;//驗證規則集合
        private IValidationHandler validationHandler;//驗證處理器

        private void DefaultValidtae()
        {
            validationResults = new List<ValidationResult>();
            ValidationContext context = new ValidationContext(this, null, null);
            Validator.TryValidateObject(this, context, validationResults, true);//預設驗證實體特性註解

            if (validationRules.Count > 0)//如果有其它驗證規則
            {
                validationRules.ForEach(o =>
                {
                    if (o.Validate() == ValidationResult.Success || o.Validate() == null)
                        return;
                    //其它規則不通過時
                    validationResults.Add(o.Validate());//添加到驗證結果集合
                });
            }
        }
        /// <summary>
        /// 驗證標識
        /// </summary>
        public virtual bool IsValid { get
            {
                DefaultValidtae();
                return validationResults.Count <= 0;
            } }

        /// <summary>
        /// 添加驗證規則
        /// </summary>
        /// <param name="rule"></param>
        public void AddValidationRule(IValidationRule rule)
        {
            if (rule == null)
                return;
            validationRules.Add(rule);
        }
        /// <summary>
        ///  預設驗證當前實體方法
        /// </summary>
        public virtual void Validate()
        {

            if (IsValid)
                return;

            //驗證不通過時            
            try
            {
                validationHandler = DependencyContainer.Resolve<IValidationHandler>();//設置驗證處理器
            }
            catch
            {
                throw new InvalidOperationException("當前實體沒有通過驗證,但未設置驗證處理器,請檢查容器是否註入");
            }
            
            validationHandler.Handle(validationResults);//處理驗證結果
            
        } 
        #endregion

    }
}
View Code

Repository數據操作介面,這裡也是搬的,但有一點不一樣,考慮到排序我不想用Guid類型的,所以沒有約束Entity必須實現IBaseEntity介面或者聚合根,因為我用的三層,沒用聚合根,再者我在後面的Service層想用到分頁排序,看到後面應該就能理解的。但是我並不知道這樣對不對

IUnitOfWork只搞了一個方法,以後再搞

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace MTF.Domain
{
     public interface IRepository<TEntity,TKey> where TEntity:class
    {
        /// <summary>
        /// 添加單個實體
        /// </summary>
        /// <param name="entity"></param>
        void Add(TEntity entity);
        /// <summary>
        /// 添加實體集合
        /// </summary>
        /// <param name="entities"></param>
        void Add(IEnumerable<TEntity> entities);
        /// <summary>
        /// 修改
        /// </summary>
        /// <param name="entity"></param>
        void Update(TEntity entity);
        /// <summary>
        /// 根據Id刪除實體
        /// </summary>
        /// <param name="id"></param>
        void Remove(TKey id);
        /// <summary>
        /// 刪除實體
        /// </summary>
        /// <param name="entity"></param>
        void Remove(TEntity entity);
        /// <summary>
        /// 查找實體列表
        /// </summary>
        /// <returns></returns>
        List<TEntity> FindAll();
        /// <summary>
        /// 查找實體列表
        /// </summary>
        /// <returns></returns>
        IQueryable<TEntity> Find();
        /// <summary>
        /// 查找實體列表
        /// </summary>
        /// <param name="ids"></param>
        /// <returns></returns>
        //List<TEntity> Find(IEnumerable<TKey> ids);
        /// <summary>
        /// 根據Id查找實體
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        TEntity Find(params object[] id);
        /// <summary>
        /// 判斷實體是否存在
        /// </summary>
        /// <param name="predicate"></param>
        /// <returns></returns>
        bool Exists(Expression<Func<TEntity, bool>> predicate);
        /// <summary>
        /// 索引器查找
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        TEntity this[TKey id] { get; }
        /// <summary>
        /// 保存
        /// </summary>
        void Save();
        /// <summary>
        /// 獲取工作單元
        /// </summary>
        /// <returns></returns>
        IUnitOfWork GetUnitOfWork();

        IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> where);
       
    }
}
View Code
using System;

namespace MTF.Domain
{
    public interface IUnitOfWork:IDisposable
    {
        void Commit();
    }
}
View Code
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace MTF.Domain
{
    public interface IValidationHandler
    {
        void Handle(ICollection<ValidationResult> validationResults);
    }
}
View Code
using System.ComponentModel.DataAnnotations;

namespace MTF.Domain
{
    public interface IValidationRule
    {
        ValidationResult Validate();
    }
}
View Code

 

Domain現在只完成了這麼多,其它的介面還沒搞,還是以後再搞,懶的。

後面是EF部分了,引用Domain層

簡單的工作單元

using MTF.Domain;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Validation;

namespace MTF.EFDatas
{
    public class EFUnitOfWork :DbContext, IUnitOfWork
    {
        protected EFUnitOfWork(string connection) : base(connection) { }
        public void Commit()
        {
            try
            {
                SaveChanges();
            }
            catch(DbUpdateConcurrencyException)
            {
                throw;
            }
            catch(DbEntityValidationException)
            {
                throw;
            }
        }
        
    }
}
EFUnitOfWork

簡單的EF數據操作,還是以後慢慢再搞

using MTF.Domain;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace MTF.EFDatas
{
    public class EFRepository<TEntity,TKey>: IRepository<TEntity,TKey> where TEntity:class
    {
        protected EFUnitOfWork unitOfWork { get; private set; }
        public EFRepository()
        {
            this.unitOfWork = DependencyContainer.Resolve<EFUnitOfWork>();
        }
        public void Add(TEntity entity)
        {
            unitOfWork.Set<TEntity>().Add(entity);
        }
        public void Add(IEnumerable<TEntity> entities)
        {
            unitOfWork.Set<TEntity>().AddRange(entities);
        }
        public void Update(TEntity entity)
        {
            unitOfWork.Entry(entity).State = System.Data.Entity.EntityState.Modified;

        }
        public void Remove(TKey id)
        {
            unitOfWork.Set<TEntity>().Remove(Find(id));
        }
        public void Remove(TEntity entity)
        {
            unitOfWork.Set<TEntity>().Remove(entity);
        }
        public List<TEntity> FindAll()
        {
            return Find().ToList();
        }
        public IQueryable<TEntity> Find()
        {
            return unitOfWork.Set<TEntity>();
        }
        //public List<TEntity> Find(IEnumerable<TKey> ids)
        //{
        //    if (ids == null)
        //        return null;
        //    return Find().Where(o => ids.Contains(o.GId)).ToList();
        //}
        public TEntity Find(params object[] id)
        {
            return unitOfWork.Set<TEntity>().Find(id);
        }
        public IQueryable<TEntity> Find(Expression<Func<TEntity,bool>> where)
        {
            return Find().Where(where);
        }
        public bool Exists(Expression<Func<TEntity, bool>> predicate)
        {
            return unitOfWork.Set<TEntity>().Any(predicate);
        }
        public TEntity this[TKey id] { get { return Find(id); } }
        public void Save()
        {
            unitOfWork.Commit();
        }
        public IUnitOfWork GetUnitOfWork()
        {
            return unitOfWork;
        }
        
    }
    
}
EFRepository

 

這裡我自己寫了一個分頁類EF專用的,因為不想一定要用ID分類,所以加了個泛型類型,專門用於排序用的,本來想著這個類是專門給service用的,不應該用public的,但是想著以後別的地方可能會用到,所以還是公共了

using System;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;

namespace MTF.EFDatas
{
    /// <summary>
    /// 分頁
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    /// <typeparam name="TOrder">要排序的類型</typeparam>
    public class Pager<TEntity,TOrder> where TEntity : class
    {
        public Pager(DbContext eow)
        {
            this.unitOfWork = eow;
            this.total = eow.Set<TEntity>().Count();
            Initia(1, 20);
        }
        private DbContext unitOfWork;
        public int PageIndex { get { return pageIndex; } }
        private int pageIndex;
        public int PageCount { get { return pageCount; } }
        private int pageCount;
        public int Total { get { return total; } }
        private int total;
        public int PageSize { get { return pageSize; } }
        private int pageSize;
        public int Skip { get { return skip; } }
        private int skip;
        public int Take { get { return take; } }
        private int take;

        private void Initia(int index, int size)
        {
            if (index < 1)
                index = 1;
            if (size >= total)
            {
                pageIndex = 1; pageCount = 1; pageSize = total; skip = 0; take = pageSize;
            }
            if (size < total)
            {
                int n = total % size; int x = total / size;
                if (n == 0)
                {
                    pageSize = size;
                    pageCount = x;

                    if (index > pageCount)
                    {
                        index = PageCount;
                        pageIndex = index;
                    }
                    pageIndex = index;

                    skip = (pageIndex - 1) * size;
                    take = size;
                }
                else
                {
                    pageCount = x + 1;
                    if (index > pageCount)
                    {
                        pageIndex = pageCount;
                    }

                    else
                    {
                        pageIndex = index;
                    }

                    if (pageIndex == pageCount)
                    {
                        pageIndex = PageCount;
                        pageSize = n;
                        skip = (pageIndex - 1) * size;
                        take = n;
                    }
                    else
                    {
                        pageSize = size;
                        skip = (pageIndex - 1) * size;
                        take = size;
                    }
                }
            }
        }
        public void SetPageSize(int size)
        {
            pageSize = size;
            Initia(PageIndex, PageSize);
        }

        public IQueryable<TEntity> GetPageList(Expression<Func<TEntity, TOrder>> order)
        {
            return unitOfWork.Set<TEntity>().OrderBy(order).Skip(Skip).Take(Take);
        }
        public IQueryable<TEntity> GetPageList(int pageIndex, Expression<Func<TEntity, TOrder>> order)
        {
            this.pageIndex = pageIndex;
            Initia(PageIndex, PageSize);
            return GetPageList(order);
        }

        public IQueryable<TEntity> GetPageList(int pageIndex, int pageSize, Expression<Func<TEntity, TOrder>> order)
        {
            this.pageIndex = pageIndex;
            this.pageSize = pageSize;
            Initia(PageIndex, PageSize);
            return GetPageList(order);
        }
    }
}
自己寫的Pager

 

直接給代碼先,都是很簡單,不知道合不合理,這裡用到了Pager類,當作屬性使用,客戶端得到Service就可以使用它的,它們共用一個工作單元

using MTF.Domain;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;

namespace MTF.EFDatas
{
    public class BaseService<TEntity,TKey,TOrderPage> where TEntity:class
    {
        private IRepository<TEntity, TKey> Repository;
        private Pager<TEntity, TOrderPage> pager;
        public Pager<TEntity,TOrderPage> Pager { get { return pager; } }
        public BaseService()
        {
            Repository = DependencyContainer.Resolve<IRepository<TEntity, TKey>>();
            pager = new Pager<TEntity, TOrderPage>(DependencyContainer.Resolve<EFUnitOfWork>());
        }
        
       
        public void Add(TEntity entity)
        {
            Repository.Add(entity);
        }
        public void Add(IEnumerable<TEntity> entities)
        {
            Repository.Add(entities);
        }
        public void Update(TEntity entity)
        {
            Repository.Update(entity);
        }
        public void Remove(TKey id)
        {
            Repository.Remove(id);
        }
        public void Remove(TEntity entity)
        {
            Repository.Remove(entity);
        }
        public List<TEntity> FindAll()
        {
            return Repository.FindAll();
        }
        public IQueryable<TEntity> Find()
        {
            return Repository.Find();
        }
        //public List<TEntity> Find(IEnumerable<TKey> ids)
        //{
        //    return Repository.Find(ids);
        //}
        public TEntity Find(params object[] id)
        {
            return Repository.Find(id);
        }
        public IQueryable<TEntity> Find(Expression<Func<TEntity,bool>> where)
        {
            return Find().Where(where);
        }
        public bool Exists(Expression<Func<TEntity, bool>> predicate)
        {
            return Repository.Exists(predicate);
        }
        public TEntity this[TKey id] { get { return Find(id); } }
        public void Save()
        {
            Repository.Save();
        }
    }
}
BaseService

由於沒有寫測試代碼,寫到這裡的時候 我都有點迫不及待想試試了,所以就先到此生成。另外寫一個MVC試試唄,搞個三層玩玩

DataModel層添加上面的DLL引用,我叫MTF指的是My Test Frame呵呵,不會英語,反正是搞的玩。

安裝EF和AUTOFAC包開搞,先搞個BaseEntity,由於我int 類型不會初始化的時候賦值,

但是又不想GUID來排序,所以我想的辦法是實體介面的時候,給的GId的屬性名,主鍵我還是用的int類型,Guid初始化的時候賦值,它同樣是唯一的,方便記憶體操作,int類型做自增的主鍵,在插入資料庫後才有值。

做了三個類,用戶,許可權和角色,還有三個中間類,以配多對多關係,本著學習的精神,我用的CodeFirst和F..Api(不會定這個單詞了,暈)方式配置映射

一樣的簡單玩玩的,可能還沒有註釋,直接上了

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MTF.Domain;

namespace OADemo.DataModel.Domains
{
    public class EntityBase:BaseEntity<Guid>
    {
        protected EntityBase(Guid id) : base(id) { }//guid類型的屬性以後應該可以用到,它是在初始化的時候賦值的,記憶體中好找
        public EntityBase() : this(Guid.NewGuid())//int類型映射主鍵,插入資料庫時才有值,方便資料庫不方便記憶體操作
        {
            SubTime = DateTime.Now;//提交時間為當前時間
            IsDeleted = false;//這個好像有點多餘,預設應該就是false,但不確定,所以加上了
        }
        //子類共有的屬性
        public int Id { get; set; }
        public bool IsDeleted { get; set; }
        public DateTime SubTime { get; set; }
        public string Remark { get; set; }
    }
}
EntityBase
using System.Collections.Generic;

namespace OADemo.DataModel.Domains
{
    public class UserEntity:EntityBase
    {
        public ICollection<UserRole> UserRoles { get; set; } = new List<UserRole>();//導航屬性
        public ICollection<UserPermission> UserPermissions { get; set; } = new List<UserPermission>();//導航屬性
        public string Name { get; set; }
        public int Age { get; set; }
        public string Email { get; set; }
        public string RealName { get; set; }
    }
    public class RoleEntity:EntityBase
    {
        public ICollection<UserRole> UserRoles { get; set; } = new List<UserRole>();
        public ICollection<RolePermission> RolePermissions { get; set; } = new List<RolePermission>();
        public string Name { get; set; }
        public string Description { get; set; }
    }
    public class	   

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 設備管理員 Device Admin 獲取DevicePolicyManager對象,通過getSystemService(DEVICE_POLICY_MANAGER),設備策略管理器 調用DevicePolicyManager對象的lockNow()方法,鎖定,此時會報 安全異常 新建一個類MyA ...
  • 在整型信號量機制中,信號量被定義為一個整形變數。除初始化外,僅能通過兩個標準的原子操作Wait(S)和Signal(S)來訪問。其通常分別被稱為P、V操作。 描述如下: P操作:S=S-1;如果S小於0,則進程進入等待狀態,否則繼續執行。 V操作:S=S+1;如果S>=0,則喚醒等待隊列中的一個等待 ...
  • 首先在開始正文之前先介紹最簡單的獲取進程/線程句柄方法。那就是可以在創建進程/線程時獲取句柄。 創建進程/線程是獲取句柄。 //進程創建函數 BOOL CreateProcess( PCTSTR pszApplicationName, PTSTR pszCommandLine, PSECURITY_... ...
  • 第一、檢查硬碟設備是否有數據盤 第二、數據硬碟分區 第三、ext3格式化分區 第四、掛載新分區 A - 新建目錄[任意創建] B - 掛載分區 第五、寫入fstab 設置開機自動掛載 第六、檢查是否掛載成功(df -h ) ...
  • bmp.c:8: warning: malformed '#pragma pack(push[, id], <n>)' - ignored bmp.c:33: warning: #pragma pack (pop) encountered without matching #pragma pack ...
  • 反射的定義:審查元數據並收集關於它的類型信息的能力。元數據(編譯以後的最基本數據單元)就是一大堆的表,當編譯程式集或者模塊時,編譯器會創建一個類定義表,一個欄位定義表,和一個方法定義表等。System.reflection命名空間包含的幾個類,允許你反射(解析)這些元數據表的代碼 System.Re ...
  • 在ASP.NET中,頁面間數據傳遞的方法有很多。下麵為大家總結一下,頁面間數據傳遞的方法。 Web頁面是無狀態的,伺服器對每一次請求都認為來自不同用戶,因此,變數的狀態在連續對同一頁面的多次請求之間或在頁面跳轉時不會被保留。在 用ASP.NET 設計開發一個Web系統時, 遇到一個重要的問題是如何保 ...
  • 前言 作為一名.NET程式員,很多時候都會被什麼拖控制項、跨平臺等字眼所鄙視過,但是在我的的內心還是沒有把自己看低過。因為說到底,平臺和語言只是我們吃飯的工具。很多時候公司的發展是取決於商業競爭的,微軟也不例外。在大環境下,我們終於看到了微軟給我們帶來的跨平臺的驚喜。儘管這隻是剛剛開始,但我相信在不久 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...