新思想、新技術、新架構——更好更快的開發現代ASP.NET應用程式(續1)
今天在@張善友和@田園裡的蟋蟀的部落格看到微軟“.Net社群虛擬大會”dotnetConf2015的資訊,感謝他們的真誠付出!真希望自已也能為中國的.NET社群貢獻綿薄之力。
上週星期天開通了部落格併發布了第一篇文章《新思想、新技術、新架構——更好更快的開發現代ASP.NET應用程式》,彙集了一些比較流行的技術和開源專案,也把自己的程式架構、部分程式碼風格、前端表現簡單做了一些展示,引起了近100位朋友的評論。特別感謝@田園裡的蟋蟀、@深藍醫生、@郭明鋒、@瘋狂的提子、@jimcsharp、@以吾之名等給我建議和指導的朋友,也感謝那些給我支援和鼓勵的朋友。還有對我提出批評的朋友,說我的面試題的內容不當,也很感謝他們讓我更注意言辭,但並不會影響我對面試者基礎知識的重視程度。
上週釋出那篇文章主要是因為這段時間在招聘過程中發現幾乎所有面試者對基礎知識和新技術都知之甚少,有過幾年工作經驗的程式設計師也幾乎只會單一模式的CURD,沒有明顯的技術特長,所以我想分享一些自己認為比較好的思想、技術、架構模式,引起更多ASP.NET程式設計師的思考和討論。
其實,上週星期天是花了大半天寫一篇部落格,在發出來之前刪掉了一大半內容(一些講述我自己心路歷程的內容),因為我在部落格園是一個新人,在沒有對別人提供價值幫助之前也許沒人關心我是誰。那天由於時間太晚了,很多想寫的內容都沒有寫出來,釋出的時候僅貼了一些圖片,後來在評論中寫了很多內容,並修改了原文正文,補充分享了一些非常好的開源專案。希望之前看過的朋友可以再回去看看,給個連結:
之前的一個專案是做的微信公眾平臺的第三方平臺,提供微網站自主建站、會員卡、微商城、外賣預訂等幾十項功能。在專案初期,我僅擔任產品總監負責產品設計,後來因為沒有強大的前端團隊,不得不親自實現微官網的視覺化設計器的前端。再後來公司讓我接管了開發部(全是JAVA開發人員),跟開發團隊有了更直接的配合。我發現他們普遍程式碼質量不高,幾乎不懂得運用設計模式和最佳實踐。每新增或修改一點功能,都要將全部程式碼進行編譯和釋出,會影響正在登入使用的使用者,而且有時候一個經驗不足的程式設計師修改的一點東西會讓整個平臺不能正常啟動。跟幾個高階工程師多次溝通,希望他們學習新技術新思想,運用成熟的最佳實踐來提高程式碼質量;希望他們瞭解領域驅動設計用於會員卡等業務較複雜的模組;希望他們能瞭解OSGI實現模組化開發和部署,但因為經驗能力和積極性等原因,這些願望都沒有實現。後來在新專案(開發代號Fami)中,我選擇了.NET技術平臺,並組建新的開發團隊來進行這個專案。現在專案才剛完成基礎框架和專案規範。
下面把這個專案的架構思想和功能特性再分享一下。希望對正在設計架構的朋友有一個參考作用。本專案是Saas模式的線上產品,需實現多租戶模式;有多個功能模組,且上線時間有先有後,需實現模組化開發。
本專案總體分為兩個部分:一個基礎框架元件,一個Fami解決方案。
基礎框架元件的功能:
1、基礎框架元件獨立、通用,可用於多個不同專案。類似於daxnet的Apworks框架。
2、對專案實現模組化開發提供了支援,每個模組有獨立的EF DbContext,可單獨指定資料庫。
3、對DDD的技術實現進行了封裝,讓專案以極精簡的程式碼,專注於業務領域。
4、多租戶支援,每個租戶的資料自動隔離,業務模組開發者不需要手動操作TenantId。
5、整合ASP.NET Identity,實現登入認證、功能許可權授權&驗證、角色和使用者管理。
6、整合Log4Net,實現日誌記錄。
7、整合AutoMapper,實現Dto類與實體類的雙向自動轉換。
8、實現UnitOfWork模式,為應用層和倉儲層的(會寫資料庫的)方法自動實現資料庫事務。
9、可通過ApplicationService的方法自動建立相應的WebApi方法,ajax可直接呼叫,不需要寫ApiController和Action。
10、呼叫ApplicationService的方法時,自動驗證許可權和引數有效性(用相應的Attribute標註)。
11、繼承自FullAuditedEntity基類的領域實體,會自動實現軟刪除(在資料庫中用IsDeleted欄位進行標註)。
12、實現一系列擴充套件方法,簡化編碼。
Fami專案解決方案結構圖:
模組化結構圖 | WEB專案結構圖 |
每個模組是一個獨立的類庫專案,有獨立的DbContext(如上面左圖中的WechatMpDbContext.cs),可單獨指定不同的資料庫連結,以實現按功能模組分庫。
每個模組有自己許可權提供類(WechatMpAuthorizationProvider.cs)、設定提供類(WechatMpSettingProvider.cs)、倉儲基類(WechatMpRepository.cs)。
模組的展現層程式碼(MVC檔案)放在WEB專案的Areas下,有自己單獨的路由註冊類檔案(如上面右圖中的WechatMpAreaRegistration.cs)。
MVC的Controller只有極少的程式碼,用於返回列表頁的View、表單頁面的View和Model,新建、編輯、刪除等操作無需寫Action方法,直接由前端的ajax呼叫Application層的相應Service方法(執行時,動態代理自動生成ApiController及相應方法)。
拿一個最最簡單的圖文素材功能舉例說明:
Domain層的Article實體類:
1 namespace Fami.WechatMp 2 { 3 public class Article : AuditedEntityAndTenant 4 { 5 [MaxLength(50)] 6 public string Title { get; set; } 7 8 [MaxLength(512)] 9 public string PicUrl { get; set; } 10 11 [MaxLength(1000)] 12 public string Interoduction { get; set; } 13 14 [MaxLength(512)] 15 public string LinkUrl { get; set; } 16 17 [MaxLength(512)] 18 public string OriginalUrl { get; set; } 19 20 public string Content { get; set; } 21 22 [ForeignKey("ArticleCategoryId")] 23 public ArticleCategory ArticleCategory { get; set; } 24 25 public Guid ArticleCategoryId { get; set; } 26 } 27 }
Application層的ArticleDto類(用於WEB前端表單與Application層之間傳值):
1 namespace Fami.WechatMp 2 { 3 [AutoMap(typeof(Article))] 4 public class ArticleDto : EntityDto, IValidate 5 { 6 [Required] 7 [MaxLength(50)] 8 public string Title { get; set; } 9 10 [MaxLength(512)] 11 public string PicUrl { get; set; } 12 13 [MaxLength(1000)] 14 public string Interoduction { get; set; } 15 16 [MaxLength(512)] 17 public string LinkUrl { get; set; } 18 19 [MaxLength(512)] 20 public string OriginalUrl { get; set; } 21 22 public string Content { get; set; } 23 24 public Guid ArticleCategoryId { get; set; } 25 } 26 }
Application層的ArticleItem類(用於WEB前端查詢列表的顯示):
1 namespace Fami.WechatMp 2 { 3 [AutoMapFrom(typeof(Article))] 4 public class ArticleItem : EntityDto 5 { 6 public string Title { get; set; } 7 8 public string PicUrl { get; set; } 9 10 public string LinkUrl { get; set; } 11 12 public string OriginalUrl { get; set; } 13 14 public string ArticleCategoryCategoryName { get; set; } //會自動讀取ArticleCategory的CategoryName屬性 15 16 public DateTime CreationTime { get; set; } 17 } 18 }
Application層的IArticleAppService介面:
1 namespace Fami.WechatMp 2 { 3 public interface IArticleAppService : IApplicationService 4 { 5 /// <summary> 6 /// 獲取素材分類列表(下拉框) 7 /// </summary> 8 /// <returns></returns> 9 Task<IEnumerable<ArticleCategoryDto>> GetArticleCategories(); 10 11 #region 素材查詢和更新操作 12 /// <summary> 13 /// 建立素材資訊 14 /// </summary> 15 /// <param name="model"></param> 16 /// <returns></returns> 17 Task<ArticleDto> CreateArticle(ArticleDto model); 18 19 /// <summary> 20 /// 更新素材資訊 21 /// </summary> 22 /// <param name="model"></param> 23 /// <returns></returns> 24 Task UpdateArticle(ArticleDto model); 25 26 /// <summary> 27 /// 批量刪除素材資訊 28 /// </summary> 29 /// <param name="input"></param> 30 /// <returns></returns> 31 Task BatchDeleteArticle(IEnumerable<Guid> idList); 32 33 /// <summary> 34 /// 獲取指定的素材資訊 35 /// </summary> 36 /// <param name="id"></param> 37 /// <returns></returns> 38 Task<ArticleDto> GetArticle(Guid id); 39 40 /// <summary> 41 /// 查詢素材列表資訊(Table) 42 /// </summary> 43 /// <param name="input"></param> 44 /// <returns></returns> 45 Task<QueryResultOutput<ArticleItem>> GetArticleList(GetArticleListInput input); 46 47 #endregion 48 } 49 }
Application層的ArticleAppService實現類:
1 namespace Fami.WechatMp 2 { 3 public class ArticleAppService : FamiAppServiceBase, IArticleAppService 4 { 5 private readonly IWechatMpRepository<ArticleCategory> _articleCategoryRepository; 6 private readonly IWechatMpRepository<Article> _articleRepository; 7 private readonly IArticlePolicy _articlePolicy; 8 9 public ArticleAppService( 10 IWechatMpRepository<ArticleCategory> articleCategoryRepository, 11 IWechatMpRepository<Article> articleRepository, 12 IArticlePolicy articlePolicy 13 ) 14 { 15 _articleCategoryRepository = articleCategoryRepository; 16 _articleRepository = articleRepository; 17 _articlePolicy = articlePolicy; 18 } 19 20 public async Task<IEnumerable<ArticleCategoryDto>> GetArticleCategories() 21 { 22 var query = _articleCategoryRepository.GetAll().OrderBy(item => item.DisplayOrder); 23 return await query.Query().To<ArticleCategoryDto>().Take(100).ToListAsync(); 24 } 25 26 public async Task<ArticleDto> CreateArticle(ArticleDto model) 27 { 28 if (await _articlePolicy.IsExistsArticleByName(model.Title)) 29 { 30 throw new UserFriendlyException(L("NameIsExists")); 31 } 32 var entity = await _articleRepository.InsertAsync(model.MapTo<Article>()); 33 return entity.MapTo<ArticleDto>(); 34 } 35 36 public async Task UpdateArticle(ArticleDto model) 37 { 38 if (await _articlePolicy.IsExistsArticleByName(model.Title, model.Id)) 39 { 40 throw new UserFriendlyException(L("NameIsExists")); 41 } 42 var entity = await _articleRepository.GetAsync(model.Id); 43 await _articleRepository.UpdateAsync(model.MapTo(entity)); 44 } 45 46 public async Task BatchDeleteArticle(IEnumerable<Guid> idList) 47 { 48 if (await _articlePolicy.IsExistsByArticleAutoreplySetting(idList.ToList())) 49 { 50 throw new UserFriendlyException(L("AutoreplyArticleIsExists")); 51 } 52 await _articleRepository.BatchDeleteAsync(idList); 53 } 54 55 public async Task<ArticleDto> GetArticle(Guid id) 56 { 57 var entity = await _articleRepository.GetAsync(id); 58 return entity.MapTo<ArticleDto>(); 59 } 60 61 /// <summary> 62 /// 根據查詢條件,返回文章列表資料 63 /// </summary> 64 /// <param name="input">查詢條件</param> 65 /// <returns></returns> 66 public async Task<QueryResultOutput<ArticleItem>> GetArticleList(GetArticleListInput input) 67 { 68 var query = _articleRepository.GetAll() 69 .WhereIf(input.ArticleCategoryId.HasValue, m => m.ArticleCategoryId == input.ArticleCategoryId.Value) 70 .WhereIf(!input.Keywords.IsNullOrWhiteSpace(), m => m.Title.Contains(input.Keywords)); 71 72 var result = await query.Query(input).ToAsync<ArticleItem>(); 73 return result; 74 } 75 } 76 }
ArticleController.cs程式碼如下:
1 namespace Fami.Mc.Web.Controllers 2 { 3 public class ArticleController : FamiControllerBase 4 { 5 private readonly IArticleAppService _articleAppService; 6 7 public ArticleController(IArticleAppService articleAppService) 8 { 9 _articleAppService = articleAppService; 10 } 11 12 public async Task<ActionResult> Index() 13 { 14 ViewBag.ArticleCategoryDtos = await _articleAppService.GetArticleCategories(); 15 return View(); 16 } 17 18 public async Task<ActionResult> Edit(Guid? id) 19 { 20 ArticleDto model; 21 if (!id.HasValue) //新建 22 { 23 model = new ArticleDto(); 24 ViewBag.ActionName = "createArticle"; 25 } 26 else //編輯 27 { 28 model = await _articleAppService.GetArticle(id.Value); 29 ViewBag.ActionName = "updateArticle"; 30 } 31 ViewBag.ArticleCategoryDtos = await _articleAppService.GetArticleCategories(); 32 return View(model); 33 } 34 } 35 }
Views/Article/Index.cshtml程式碼(列表頁):
1 <div class="page-content"> 2 <div class="page-header"> 3 <div class="page-title">文章管理</div> 4 <!-- 過濾條件start --> 5 <div id="filterbar" class="alert alert-lightsGray fs12 clearfix"> 6 <div class="clearfix" style="margin-right:30px;"> 7 <div class="clearfix pull-left" style="line-height: 30px; margin: 3px 5px; "> 8 <div class="pull-left">分類:</div> 9 <div class="pull-left"> 10 @Html.DropDownList("ArticleCategoryId", new SelectList(ViewBag.ArticleCategoryDtos, "Id", "CategoryName"), "", new { @class = "form-control w180"}) 11 </div> 12 </div> 13 <div class="clearfix pull-left" style="line-height: 30px; margin: 3px 5px;"> 14 <div class="pull-left">搜尋:</div> 15 <div class="input-group input-group-sm w130"> 16 <input class="form-control pull-left" placeholder="文章標題" filterfield="Keywords" name="Keywords" type="text"> 17 <span class="input-group-btn"> 18 <button class="btn btn-default btnSearch" type="button"><i class="icon-search2 fs14"></i></button> 19 </span> 20 </div> 21 </div> 22 </div> 23 </div> 24 <!-- 過濾條件end --> 25 </div> 26 27 <!-- 列表上的功能按鈕放在這裡 --> 28 <div class="buttons-panel"> 29 <button id="btnNew" class="btn btn-primary"><i class="icon-plus2"></i>新增文章</button> 30 <button id="btnEdit" class="btn btn-default"><i class="icon-edit"></i>編輯</button> 31 <button id="btnDeletes" class="btn btn-default"><i class="icon-trash"></i>刪除 </button> 32 <button id="btnReload" class="btn btn-default"><i class="icon-refresh"></i>重新整理 </button> 33 </div> 34 <table id="mytable" class="wx-listview table table-bordered"></table> 35 </div> 36 @section js{ 37 @Scripts.Render("~/js/datatables") 38 <script src="~/Areas/WechatMp/js/article.js"></script> 39 }
article.js程式碼:
1 var listColumns = [ 2 listCheckboxColumn, 3 { "name": "id", "data": "id", title: "ID", "sortable": false, "visible": false }, 4 { "name": "title", "data": "title", title: "名稱" }, 5 { 6 "name": "picUrl", "data": "picUrl", title: "圖片", "width": "100", "sortable": false, 7 "render": function (data) { return '<img src="' + abp.resourcePath + data + '" style="width:60px;"/>';} 8 }, 9 { "name": "articleCategoryCategoryName", "data": "articleCategoryCategoryName", title: "所屬分類" }, 10 { "name": "linkUrl", "data": "linkUrl", title: "外鏈地址" }, 11 { "name": "originalUrl", "data": "originalUrl", title: "原文地址" }, 12 { "name": "creationTime", "data": "creationTime", title: "建立時間", "width": "180" } 13 ]; 14 15 $(function () { 16 abp.grid.init({ 17 order: [[abp.grid.getColIndex("creationTime"), "desc"]], 18 filterbar: "#filterbar",//過濾區域selector 19 table: "#mytable",//table selector 20 ajax: abp.grid.ajaxLoadEx({ 21 "url": abp.appPath + "api/wechatmp/article/getArticleList", 22 }), 23 columns: listColumns 24 }); 25 26 //新增 27 $("#btnNew").click(function () { 28 abp.dialog({ 29 width: "900px", 30 title: "新增文章", 31 href: abp.appPath + 'WechatMp/Article/Edit', 32 callback: abp.grid.reloadList 33 }); 34 }); 35 36 //編輯 37 $("#btnEdit").on('click', function () { 38 var row = abp.grid.getSelectedOneRowData(); 39 if (!row) return; 40 abp.dialog({ 41 width: "900px", 42 title: "編輯分類", 43 href: abp.appPath + 'WechatMp/Article/Edit/' + row.id, 44 callback: abp.grid.reloadList 45 }); 46 }); 47 48 //刪除 49 $("#btnDeletes").on('click', function () { 50 var idList = abp.grid.getSelectedIdList(); 51 if (idList.length == 0) return; 52 53 abp.confirm(abp.utils.formatString("您確認要刪除選中的{0}行嗎?", idList.length), function (result) { 54 if (!result) return; //取消 55 abp.ajax({ 56 url: abp.appPath + 'api/wechatmp/article/batchDeleteArticle', 57 data: idList 58 }).done(function (ret) { 59 abp.success("刪除成功"); 60 abp.grid.reloadList(); 61 }); 62 }); 63 }); 64 })
介面截圖:
在進行這個列表查詢時,客戶端ajax直接呼叫ArticleAppService的GetArticleList方法,看下瀏覽器請求:
會根據文章分類的下拉選項,自動生成ArticleCategoryId的查詢過濾引數。
服務端執行GetArticleList方法,自動把客戶端ajax提交的資料組裝成input引數(GetArticleListInput類指定的結構),然後根據過濾條件進行查詢:
1 /// <summary> 2 /// 根據查詢條件,返回文章列表資料 3 /// </summary> 4 /// <param name="input">查詢條件</param>相關推薦
新思想、新技術、新架構——更好更快的開發現代ASP.NET應用程式(續1)
今天在@張善友和@田園裡的蟋蟀的部落格看到微軟“.Net社群虛擬大會”dotnetConf2015的資訊,感謝他們的真誠付出!真希望自已也能為中國的.NET社群貢獻綿薄之力。 上週星期天開通了部落格併發布了第一篇文章《新思想、新技術、新架構——更好更快的開發現代ASP.NET應用程式》,彙集了一些比較流
新思想、新技術、新架構——更好更快的開發現代ASP.NET應用程式
在部落格園學習很長時間了,今天終於自己也開通了部落格,準備分享一些感悟和經驗。首先感謝部落格園園主提供了這麼好的程式設計師學習交流平臺,也非常感謝張善友、dax.net、netfocus、司徒正美 等技術大牛的無私分享,從他們身上學到了很多。還有我最近一直關注的田園裡的蟋蟀,分享了很多新的技術和思想方法,在此
AI 新技術革命將如何重塑就業和全球化格局?深度解讀 UN 報告(上篇)
作者:騰訊研究院 中國《新一代人工智慧發展規劃》開篇即表明,人工智慧的迅速發展將深刻改變人類社會生活、改變世界。誠然,被視為一種變革性技術的人工智慧,有望成為通用技術(GPT)並引領新技術革命,第四次工業革命(4IR)呼之欲出。與此同時,對人工智慧和機器
OAuth2.0學習(5-1)新浪開放平臺-微博OAuth2.0認證
com blank weibo mage pen auth 平臺 target img http://open.weibo.com/wiki/%E9%A6%96%E9%A1%B5 OAuth2.0學習(5-1)新浪開放平臺-微博OAuth2.0認證
新濠環主管招商遊戲設計的三個終極問題如何設計遊戲才會好玩(理論篇)
主管 招商 玩家 大戶首選 遊戲應該是所有表達媒介中最為復雜的,直擊人性又包含社會、市場、交互等諸多因素。不長的發展歷史中,鮮有科學而有效的研究成果,大量充斥的皆是所謂經驗之談,通常只適用於某類用戶或是某種遊戲。 作為遊戲設計理論的重度愛好者、創業4年,擔任過各類遊戲的研發一線制作人,我一
java 插入新的陣列項(演算法—1)
public static void main(String[] args) { /** * 插入演算法 */ String[] musics = {"he", "together" ,"world" ,"bitch" }; Arrays.sort(musics
VPN的實現技術(隧道技術、加密技術、祕鑰管理技術、身份認證技術)
為了在Internet等公共網路基礎設施上高效、安全的實現資料傳輸,VPN綜合利用了隧道技術、加密技術、祕鑰管理技術和身份認證技術。 1、隧道技術是VPN的核心技術,VPN的所有實現都是依賴於隧道。隧道主要利用協議的封裝來實現的。即用一種網路協議來封裝另一種網路協議的報文。 &
ABP(現代ASP.NET樣板開發框架)系列之3、ABP分層架構
基於DDD的現代ASP.NET開發框架--ABP系列之3、ABP分層架構 ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱。 前言 為了減少複雜性和提高程式碼的可重用性,採用分層架構是一種被廣泛接受的技術。為了實現分層的
人工智慧(7)---一文讀懂人臉識別技術:商業應用、產品落地、核心技術、市場規模
一文讀懂人臉識別技術:商業應用、產品落地、核心技術、市場規模 導讀:國際權威市場洞察報告Gen Market Insights近日釋出《全球人臉識別裝置市場研究報告》稱,中國2017年人臉識別產值佔全世界29.29%市場份額,2023年將達到44.59%。報告還提到中國
四、資源和物件的區別、抽象類和抽象方法、過載技術、介面interface
一、資源和物件的辨析 熟悉的資源: $link = mysql_connect(“localhost”, “root”, “123”); //得到一個“連線到mysql資料庫”的資源。 $result = mysql_query(“select ..
《Java核心技術(卷1)》筆記:第7章 異常、斷言和日誌
## 1. 異常 1. (P 280)異常處理需要考慮的問題: * 使用者輸入錯誤 * 裝置錯誤 * 物理限制 * 程式碼錯誤 2. (P 280)傳統的處理錯誤的方法是:返回一個特殊的**錯誤碼**,常見的是返回-1或者`null`引用 3. (P 280)在Java中,方法
對於程式設計師來說,技術經理與架構師哪個更有發展前途?
在程式設計這個行業,有很多爭議的話題,比如大公司還是小公司更適合程式設計師發展,哪種開發語言好,做專才程式設計師還是通才程式設計師好等等,這些話題都比較有爭議性,因為沒有標準答案,需要看具體情況,也需要結合程式設計師自身的情況,但是有一個話題是沒有爭議的,年齡大的程式設計師就會比較少,比如說
Java併發(十八):阻塞佇列BlockingQueue BlockingQueue(阻塞佇列)詳解 二叉堆(一)之 圖文解析 和 C語言的實現 多執行緒程式設計:阻塞、併發佇列的使用總結 Java併發程式設計:阻塞佇列 java阻塞佇列 BlockingQueue(阻塞佇列)詳解
阻塞佇列(BlockingQueue)是一個支援兩個附加操作的佇列。 這兩個附加的操作是:在佇列為空時,獲取元素的執行緒會等待佇列變為非空。當佇列滿時,儲存元素的執行緒會等待佇列可用。 阻塞佇列常用於生產者和消費者的場景,生產者是往佇列裡新增元素的執行緒,消費者是從佇列裡拿元素的執行緒。阻塞佇列就是生產者
奇數魔方陣、4N魔方陣、2(2N+1)魔方陣
奇數魔方陣 說明:將1到n(為奇數)的數字排列在nxn的方陣上,且各行、各列與各對角線的和必須相同,如下所示: 解法:填魔術方陣的方法以奇數最為簡單,第一個數字放在第一行第一列的正中央,然後向右(左)上填,如果右(左)上已有數字,則向下填,如下圖所示。一般程式
C# ASP.NET 優化程式效能、降低記憶體使用、提高程式執行速度
首先紀念一下今天的股票大跌抓個圖,雖然我自己損失不是很大,但是應該大多人都損失不小、也可能有人會繼續跳樓,也可能是股市一個新的轉折點來了。 接著還是重點關注自己寫程式碼優化的主題吧、軟體系統當訪問量不大、資料量不大時、程式寫得好與壞的差別,但是每天有上萬人使用時那程式碼寫得好與壞就差別很大了。 優
一、ASP.NET MVC 路由(一)--- ASP.NET WebForm路由模擬
ASP.NET WebForm 應用,使用者請求的是物理檔案,其中包括靜態頁面和動態頁面,在Url中的顯示都是伺服器中一個物理檔案的相對路徑。但是ASP.NET MVC就不同了,使用者請求的是Controller中一個Action方法,這種請求是通過路由將Url對映到相對的Controller
.NET應用程式除錯:原理、工具、方法
閱讀目錄: 1.背景介紹 2.基本原理(Windows除錯工具箱、.NET除錯擴充套件SOS.DLL、SOSEX.DLL) 2.1.Windows除錯工具箱 2.2..NET除錯擴充套件包,SOS.DLL、SOSEX.DLL 2.3.除錯系統的基本流程及架構(.NETDAC概念、mscordacwks.
.NET應用程式除錯—原理、工具、方法
閱讀目錄: 1.背景介紹 2.基本原理(Windows除錯工具箱、.NET除錯擴充套件SOS.DLL、SOSEX.DLL) 2.1.Windows除錯工具箱 2.2..NET除錯擴充套件包,SOS.DLL、SOSEX.DLL 2.3.除錯系統的基本流程及架構(.NETDAC概念、msc
【Python】matplotlib畫圖設定標題、軸標籤、刻度、刻度標籤(系列1)
摘要 資訊視覺化(也叫繪圖)是資料分析中最重要的工作之一。它可能是探索過程的一部分,例如,幫助我們找出異常值、必要的資料轉換、得出有關模型的idea等。另外,做一個可互動的資料視覺化也許是工作的最終目標。Python有許多庫進行靜態或動態的資料視覺化,但我這裡重要關注於matplotli
Android外掛化開發之AMS與應用程式(客戶端ActivityThread、Instrumentation、Activity)通訊模型分析
今天主要分析下ActivityManagerService(服務端) 與應用程式(客戶端)之間的通訊模型,在介紹這個通訊模型的基礎上,再 簡單介紹實現這個模型所需要資料型別。 本文所介紹內容基於android2.2版本。由於Android版本的不同