Asp.net(二)業(yè)務(wù)處理接口項(xiàng)目(Web Api)

簡介

Api作為業(yè)務(wù)邏輯提供方艳汽,承載了項(xiàng)目的核心邏輯州袒,因而具有相對高的邏輯復(fù)雜性勋功。在這樣的前提下如何簡化代碼編寫蹬音,如何規(guī)范統(tǒng)一書寫風(fēng)格和邏輯規(guī)范,如何提高代碼的維護(hù)性和擴(kuò)展性休玩。項(xiàng)目的搭建的高內(nèi)聚低耦合變得重要著淆。
示例的是一個(gè)企業(yè)級項(xiàng)目劫狠,框架圖如下

api層.jpg

Security:重寫了Http請求(Override DelegatingHandler),在請求的切面進(jìn)行合法性判斷永部,順便進(jìn)行簽名要求的預(yù)處理独泞。
Client:定義了統(tǒng)一的接口調(diào)用方式共調(diào)用端使用,簡化及統(tǒng)一了接口使用苔埋。
Ctrl層:作為服務(wù)的直接提供方懦砂,在服務(wù)器上直接提供類似于RestFul風(fēng)格的接口(感覺嚴(yán)格的RestFul風(fēng)格,需要有完備的領(lǐng)域模型驅(qū)動(dòng)组橄,實(shí)際上的情況總是不盡如人意荞膘,領(lǐng)域抽象能力不夠。)玉工,獲取請求數(shù)據(jù)羽资,按需調(diào)用Filter過濾器,進(jìn)一步判斷遵班,調(diào)用
Model層:作為業(yè)務(wù)模型層屠升,提供業(yè)務(wù)邏輯的實(shí)際操作。使用統(tǒng)一的實(shí)體模型狭郑,并聯(lián)系到Ibatis上腹暖,進(jìn)行數(shù)據(jù)操作。
具體的代碼結(jié)構(gòu)如下圖:


Api-UML.jpg

下面是各個(gè)模塊的詳細(xì)介紹和代碼示例:

Entity library項(xiàng)目代碼示例

項(xiàng)目結(jié)構(gòu)如下圖:

entity.jpg

Domain模塊翰萨,作為實(shí)體模型脏答,簡易代碼如下

public class User
{
      public int Id { get; set; }
      public string NickName { get; set; }
      public string Avatar { get; set; }
}

Request,請求結(jié)構(gòu)模型缨历,利用了泛型接口以蕴,將請求類和返回類聯(lián)系,起到了控制倒轉(zhuǎn)的作用辛孵。

public abstract class AbstractRequest
{
    public bool ValidateParameters()
    {
        //公用方法示例丛肮,驗(yàn)證參數(shù)合法性
    }
}
   public interface IRequest<T> where T:AbstractResponse
    {
        //獲取接口名稱
        string GetApiName();

        //獲取接口編碼
        string GetApiCode();
    }
//獲取User信息的請求結(jié)構(gòu)定義
  public class GetUserRequest:AbstractRequest,IRequest<GetUserResponse>
    {
        public int Id { get; set; }

        public string GetApiName()
        {
            return "User.GetUserDetail";
        }

        public string GetApiCode()
        {
            return "User001";
        }
    }

Response模塊,作為請求的返回類型魄缚,定義統(tǒng)一的返回結(jié)構(gòu)宝与,便于消費(fèi)者進(jìn)行一致性返回碼判斷處理。

public abstract class AbstractResponse
    {
        //返回碼
        public int Code { get; set; }
        //報(bào)錯(cuò)信息
        public string Message { get; set; }
    }
 public class GetUserResponse:AbstractResponse
    {
        public User User { get; set; }
    }
Service項(xiàng)目代碼示例

項(xiàng)目結(jié)構(gòu)如下圖:

service.jpg

代碼示例:

 public interface IUserService
    {
        GetUserResponse GetUser(int id);
    }
 public class BaseService
    {
        //protected SqlInstance sqlInstance;
        public BaseService()
        {
            //sqlInstance=new SqlInstance(); //實(shí)例化數(shù)據(jù)庫連接
            //...
        }
        //...
    }
  public class UserService:BaseService,IUserService
    {
        public GetUserResponse GetUser(int id)
        {
            //鏈接數(shù)據(jù)庫獲取數(shù)據(jù)
            //...
            throw new NotImplementedException();
        }
    }
Security類庫代碼示例

類庫只是處理了安全性問題冶匹,在api請求入口處添加上權(quán)限判斷习劫。使用重寫Http請求的方式。
代碼示例

public class MyHandler : DelegatingHandler
    {
        protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            IEnumerable<string> keyEnumerable;
            var t1 = request.Headers.TryGetValues("key", out keyEnumerable);
            var key = keyEnumerable.FirstOrDefault();
            if (!true)//驗(yàn)證類似于token的權(quán)限
            {
                return await Task.Factory.StartNew<HttpResponseMessage>(
                            () => new HttpResponseMessage(HttpStatusCode.Forbidden)
                            {
                                Content = new StringContent("error message")
                            });
            }
            //如果有signature嚼隘,判斷诽里,并加結(jié)果標(biāo)志,沒有的話飞蛹,清除signature相關(guān)信息谤狡,防止偽造灸眼。
            //.....
            return await base.SendAsync(request, cancellationToken);
        }
    }

抽象出來的權(quán)限判斷,可直接調(diào)用到webapi端墓懂,添加到路由配置代碼中焰宣。

WebApi項(xiàng)目示例

作為接口的實(shí)際定義,webapi定義了接口文件的實(shí)際規(guī)則捕仔,并做出相應(yīng)的安全管理及接口的權(quán)限控制匕积。學(xué)習(xí)微信的權(quán)限控制,大概確定了幾種接口:

接口權(quán)限.png

這些權(quán)限的判斷都放在了Security做了集中管理榜跌。接口定義只需要在相應(yīng)的邏輯上使用判斷合法性即可闪唆。
代碼示例:

public class UserController : ApiController
    {
        private IUserService userService;

        public UserController()
        {
            userService=new UserService();
        }

        [Signature]//安全簽名過濾器判斷
        [HttpPost]
        public GetUserResponse GetUser(GetUserRequest request)
        {
            //參數(shù)判斷,安全性判斷等等
            var ret = userService.GetUser(request.Id);
            return ret;
        }

    }

以上是一個(gè)獲取用戶信息的示例接口斜做,而作為接口入口的路由配置苞氮,則需要對請求的合法性進(jìn)行判斷,路由配置代碼如下:

public static void Register(HttpConfiguration config)
{
            // Web API configuration and services
            // Configure Web API to use only bearer token authentication.
            config.SuppressDefaultHostAuthentication();
            config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{action}",
                defaults: new { id = RouteParameter.Optional }
            );
            //添加的代碼瓤逼,添加http請求的入口處理
            config.MessageHandlers.Add(new MyHandler());
}
Client類庫代碼示例

Client類庫定義了接口調(diào)用的公共方法笼吟。
1、利用泛型接口霸旗,將請求類和返回類進(jìn)行了封裝贷帮,簡化調(diào)用的代碼書寫。
2诱告、并使得消費(fèi)者調(diào)用接口需要通過代理類撵枢,避開了跨域的問題。
3精居、消費(fèi)者的調(diào)用都同意使用統(tǒng)一類庫锄禽,是的日志的處理統(tǒng)一,返回的錯(cuò)誤也可以進(jìn)行一致化定義靴姿。
代碼示例如下:

 public interface IClient
 {
     T Execute<T>(IRequest<T> request) where T : AbstractResponse;
 }

public class DefaultClient:IClient
    {
        private readonly string appKey;
        private readonly string appSecret;
        private readonly string baseUrl = "http://localhost:16469/api/";
        private readonly bool isNeedLogFile = false;
        private readonly LogFile logFile;
        public static readonly string SecureHeaderAppKey = "__secure_head_appkey__";
        public static readonly string SecureHeaderSignature = "__secure_head_signature__";

        public DefaultClient()
        {
            baseUrl = ConfigurationManager.AppSettings["service_base_url"];
            appKey = ConfigurationManager.AppSettings["app_key"];
            appSecret = ConfigurationManager.AppSettings["app_secret"];
            isNeedLogFile = "1".Equals(ConfigurationManager.AppSettings["client_log_file"]);
            logFile = new LogFile("client_log_path");
            logFile.SubPath = appKey;
        }

        public DefaultClient(string serviceBase, string code, string key)
        {
            baseUrl = serviceBase;
            appKey = code;
            appSecret = key;
        }
        public T Execute<T>(IRequest<T> request) where T : AbstractResponse
        {
            var webRequest = (HttpWebRequest)WebRequest.Create(baseUrl + request.GetApiName());
            webRequest.Method = "POST";

            string reqJson;
            string sign;
            using (Stream rs = webRequest.GetRequestStream())
            {
                reqJson = JsonConvert.SerializeObject(request);

                byte[] reqBytes = Encoding.UTF8.GetBytes(reqJson);
                rs.Write(reqBytes, 0, reqBytes.Length);
                rs.Close();
            }

            webRequest.ContentType = "application/json";
            webRequest.Headers.Add(SecureHeaderAppKey, appKey);
            sign = ComputeHash(appKey, appSecret, reqJson);
            webRequest.Headers.Add(SecureHeaderSignature, sign);

            //記錄日志
            if (isNeedLogFile)
            {
                logFile.Log(string.Format("[{0}] 請求內(nèi)容: {1}", request.GetApiCode(), reqJson));
                logFile.Log(string.Format("[{0}] 請求簽名: {1}", request.GetApiCode(), sign));
            }

            try
            {
                using (var resp = (HttpWebResponse)webRequest.GetResponse())
                {
                    try
                    {
                        Stream respStream = resp.GetResponseStream();

                        if (respStream == null)
                        {
                            throw new WebException("GetResponseStream returned null");
                        }
                        var streamReader = new StreamReader(respStream);
                        string respStr = streamReader.ReadToEnd();
                        //記錄日志
                        if (isNeedLogFile)
                        {
                            logFile.Log(string.Format("[{0}] 響應(yīng)內(nèi)容: {1}", request.GetApiCode(), respStr));
                        }
                        return JsonConvert.DeserializeObject<T>(respStr);
                    }
                    catch (Exception e)
                    {
                        //記錄日志
                        if (isNeedLogFile)
                        {
                            logFile.Log(string.Format("[{0}] 響應(yīng)錯(cuò)誤: {1}", request.GetApiCode(), e.Message));
                        }
                        throw new ApplicationException(e.Message, e);
                    }
                }
            }
            catch (WebException e)
            {
                var errMsg = new StreamReader(e.Response.GetResponseStream()).ReadToEnd();
                //記錄日志
                if (isNeedLogFile)
                {
                    logFile.Log(string.Format("[{0}] 請求錯(cuò)誤: {1}", request.GetApiCode(), errMsg));
                }
                throw new APIServiceException(errMsg);
            }
        }
        private string ComputeHash(string key, string secret, string body)
        {
            return
                Convert.ToBase64String(
                    SHA1.Create().ComputeHash(Encoding.Default.GetBytes(string.Concat(key, secret, body.Trim()))));
        }
    }

以上就是Api項(xiàng)目端的各個(gè)核心環(huán)節(jié)的詳細(xì)介紹沃但。
接下來會(huì)對調(diào)用端即前端進(jìn)行簡單的介紹。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末佛吓,一起剝皮案震驚了整個(gè)濱河市宵晚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌维雇,老刑警劉巖淤刃,帶你破解...
    沈念sama閱讀 222,464評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異吱型,居然都是意外死亡逸贾,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來耕陷,“玉大人掂名,你說我怎么就攤上這事∮茨” “怎么了?”我有些...
    開封第一講書人閱讀 169,078評論 0 362
  • 文/不壞的土叔 我叫張陵锌介,是天一觀的道長嗜诀。 經(jīng)常有香客問我,道長孔祸,這世上最難降的妖魔是什么隆敢? 我笑而不...
    開封第一講書人閱讀 59,979評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮崔慧,結(jié)果婚禮上拂蝎,老公的妹妹穿的比我還像新娘。我一直安慰自己惶室,他們只是感情好温自,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,001評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著皇钞,像睡著了一般悼泌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上夹界,一...
    開封第一講書人閱讀 52,584評論 1 312
  • 那天馆里,我揣著相機(jī)與錄音,去河邊找鬼可柿。 笑死鸠踪,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的复斥。 我是一名探鬼主播营密,決...
    沈念sama閱讀 41,085評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼永票!你這毒婦竟也來了卵贱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,023評論 0 277
  • 序言:老撾萬榮一對情侶失蹤侣集,失蹤者是張志新(化名)和其女友劉穎键俱,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體世分,經(jīng)...
    沈念sama閱讀 46,555評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡编振,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,626評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片踪央。...
    茶點(diǎn)故事閱讀 40,769評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡臀玄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出畅蹂,到底是詐尸還是另有隱情健无,我是刑警寧澤,帶...
    沈念sama閱讀 36,439評論 5 351
  • 正文 年R本政府宣布液斜,位于F島的核電站累贤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏少漆。R本人自食惡果不足惜臼膏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,115評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望示损。 院中可真熱鬧渗磅,春花似錦、人聲如沸检访。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽烛谊。三九已至风响,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間丹禀,已是汗流浹背状勤。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留双泪,地道東北人持搜。 一個(gè)月前我還...
    沈念sama閱讀 49,191評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像焙矛,于是被迫代替她去往敵國和親葫盼。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,781評論 2 361

推薦閱讀更多精彩內(nèi)容