記一次.NET代碼重構

来源:http://www.cnblogs.com/jiekzou/archive/2016/12/19/6187932.html
-Advertisement-
Play Games

好久沒寫代碼了,終於好不容易接到了開發任務,一看時間還挺充足的,我就慢慢整吧,若是遇上趕進度,基本上直接是功能優先,完全不考慮設計。你可以認為我完全沒有追求,當身後有鞭子使勁趕的時候,神馬設計都是浮雲,按時上線才是王道,畢竟領導是不會關註過程和代碼質量的,領導只看結果,這也許就是我等天朝碼農的悲哀。 ...


    好久沒寫代碼了,終於好不容易接到了開發任務,一看時間還挺充足的,我就慢慢整吧,若是遇上趕進度,基本上直接是功能優先,完全不考慮設計。你可以認為我完全沒有追求,當身後有鞭子使勁趕的時候,神馬設計都是浮雲,按時上線才是王道,畢竟領導是不會關註過程和代碼質量的,領導只看結果,這也許就是我等天朝碼農的悲哀。

    需求:是這樣的,要開發一個簡訊發送的模板,不同客戶可能會使用不同的模板,而不同的客戶使用的變數參數也是不同的。之前為了應急,線上已經完成了一個簡訊模板發送簡訊的功能,簡訊模板表也創建了,而且在表中已經新增了一條記錄。我只需要做一個簡訊模板的增刪改查界面就可以了,看上去我的任務挺簡單的,老司機應該知道,接了個爛攤子。

    下圖所示是原來已經創建好了的表

   SQL創建腳本如下:

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[MessageModule](
    [Id] [uniqueidentifier] NOT NULL,
    [Name] [nvarchar](50) NULL,
    [Type] [nvarchar](50) NULL,
    [TypeNo] [nvarchar](50) NULL,
    [Channel] [nvarchar](50) NULL,
    [Param] [nvarchar](50) NULL,
    [Content] [nvarchar](max) NULL,
    [CreatedBy] [uniqueidentifier] NULL,
    [CreatedOn] [datetime] NULL,
    [ModifiedBy] [uniqueidentifier] NULL,
    [ModifiedOn] [datetime] NULL,
    [IsDeleted] [bit] NULL,
    [TypeId] [uniqueidentifier] NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

    在這之前是已經開發了一個發送簡訊的API介面供客戶調用了的,也就是說調用方(客戶),不會修改代碼,只能我這邊來修改。雖然極不情願接做了一半的任務,但是沒辦法,不可能給你的開發任務都是從頭開始的。

實體類代碼如下:

     [Table("dbo.MessageModule")]
    public class MessageModule : DTO
    {
        public string Type { get; set; } //業務類型
        public string TypeNo { get; set; } //業務編號
        public string Channel { get; set; } //使用渠道
        public string Name { get; set; } //名稱模版
        public string Content { get; set; } //簡訊內容
    }

    DOT類:

    public class DTO
    {
        public virtual Guid Id { get; set; }
        public virtual DateTime? CreatedOn { get; set; }
        public virtual Guid? CreatedBy { get; set; }
        public virtual DateTime? ModifiedOn { get; set; }
        public virtual Guid? ModifiedBy { get; set; }
        public virtual bool IsDeleted { get; set; }
    }

    這是之前的代碼,業務實體類MessageModuleBusiness.cs代碼如下:

    public class MessageModuleBusiness : GenericRepository<Model.MessageModule>
    {
        private UnitOfWork.UnitOfWork unitOfWork = new UnitOfWork.UnitOfWork();

        #region old code
        /// <summary>
        /// 獲取模版內容
        /// </summary>
        /// <param name="crowd"></param>
        /// <returns></returns>
        public string GetContent(MessageContext messageContext)
        {
            string messageContent = "";
            string TypeCode = string.IsNullOrEmpty(messageContext.serviceCode) ? "001" : messageContext.serviceCode;
            try
            {
                var Module = unitOfWork.MessageModule.Get(c => c.Type == messageContext.channel && c.TypeNo == TypeCode).FirstOrDefault();
//Content的內容:【一應生活】您有一件單號為expressNumbers company,已到communityName收發室,請打開一應生活APP“收發室”獲取取件碼進行取件。點擊下載http://a.app.qq.com/o/simple.jsp?pkgname=com.ening.life
if (!string.IsNullOrEmpty(Module.Content)) { var content = Module.Content; content = content.Replace("company", messageContext.company); content = content.Replace("expressNumbers", messageContext.expressNumbers); content = content.Replace("communityName", messageContext.communityName); content = content.Replace("Id", messageContext.Id); content = content.Replace("receiveTime", messageContext.receiveTime); content = content.Replace("fetchCode", messageContext.fetchCode); messageContent = content; } return messageContent; } catch (Exception ex) { } return ""; } #endregion }

    MessageContext類,這個是客戶端傳輸過來調用的一個實體對象。對象裡面存在許多類似於簡訊的動態標簽變數。

    public class MessageContext
    {
        /// <summary>
        /// 手機號碼
        /// </summary>
        public string phone { get; set; }
        /// <summary>
        /// 發送信息
        /// </summary>
        public string message { get; set; }
        /// <summary>
        /// 簽名
        /// </summary>
        public string sign { get; set; }
        /// <summary>
        /// 渠道
        /// </summary>
        public string channel { get; set; }
        /// <summary>
        /// 內容
        /// </summary>
        public string content { get; set; }
        /// <summary>
        /// 取件碼
        /// </summary>
        public string fetchCode { get; set; }
        /// <summary>
        /// 快遞公司
        /// </summary>
        public string company { get; set; }
        /// <summary>
        /// 快遞單號
        /// </summary>
        public string expressNumbers { get; set; }
        /// <summary>
        /// 社區名稱
        /// </summary>
        public string communityName { get; set; }
        /// <summary>
        /// 到件時間
        /// </summary>
        public string receiveTime { get; set; }
        /// <summary>
        /// 序號
        /// </summary>
        public string Id { get; set; }
        /// <summary>
        /// 業務代碼
        /// </summary>
        public string serviceCode { get; set; }
    }

    控制器方法externalMerchantSendMessage,這是供外部調用的

        /// <summary>
        /// 外部商戶發送信息
        /// </summary>
        /// <returns></returns>
        public ActionResult externalMerchantSendMessage(MessageContext param)
        {
            logger.Info("[externalMerchantSendMessage]param:" + param);
            bool isAuth = authModelBusiness.isAuth(param.channel, param.phone, param.sign);
            if (!isAuth)
            {
                return Json(new Result<string>()
                {
                    resultCode = ((int)ResultCode.NoPermission).ToString(),
                    resultMsg = "簽名或無許可權訪問"
                }, JsonRequestBehavior.AllowGet);
            }
            var meaage = messageModuleBusiness.GetContent(param);

            if (string.IsNullOrEmpty(meaage))
            {
                return Json(new Result<string>()
                {
                    resultCode = ((int)ResultCode.failure).ToString(),
                    resultMsg = "發送失敗"
                }, JsonRequestBehavior.AllowGet);
            }

            SMSHelper helper = new SMSHelper();
            helper.SendSMS(meaage, param.phone);
            return Json(new Result<string>()
            {
                resultCode = ((int)ResultCode.success).ToString(),
                resultMsg = "發送成功"
            }, JsonRequestBehavior.AllowGet);
        }

    以上是我接收開發任務之前已經實現了的功能。看上去我的任務挺簡單的,可是多年的開發經驗告訴我,這裡需要重構,如果我現在啥都不管,就只管做一個簡訊模板的增刪改查界面的話,後面維護的人一定會抓狂。

    看出什麼問題沒有?

    這個介面方法externalMerchantSendMessage是給所有客戶調用,而不同客戶使用不同的簡訊模板,不同的模板,又存在不同的變數參數。而現在所有的變數參數都封裝在了類MessageContext中,問題是我們無法一下子把所有的變數參數全部確定下來,並保持不變。那麼,也就是說一旦需要添加變數參數,類MessageContext中的代碼就必須修改,而且GetContent方法中的代碼是硬編的,一樣需要跟著修改。這樣就形成了一個迴圈,不斷加變數參數,不斷改代碼,不斷發佈介面版本.......

    時間充裕的情況下,我自然是一個有節操的程式猿,那麼就開始重構吧。

    在重構之前,在腦海浮現的並不是各種設計模式,而是面向對象設計的基本原則。各種設計模式就好比各種武學套路或者招式,習武之人應該像張無忌練習太極劍一樣,先學會各種套路,然後忘記所有套路,從而融會貫通。因為招式是死的,人是活得,有招就有破綻,根本沒有必勝招式存在,就好像沒有萬能的設計模式一樣,任何設計模式都存在缺點。

    面向對象設計的核心思想就是封裝變化,那麼先找出變化點。從上面的分析中,我們已經發現了變化點,那就是簡訊模板中的變數參數,而這些變數參數都是客戶調用方傳過來的,不同客戶傳遞的參數變數又可能是不一樣的。我們先來看一下,客戶傳遞過來的是什麼?我們看下客戶調用代碼,這裡有Get和Post兩種調用方式。

        function sendMsg() {
            //var appParam ="phone=15914070649&sign=78a7ce797cf757916c2c7675b6865b54&channel=weijiakeji&content=&fetchCode=1
&company=%E9%A1%BA%E4%B8%B0%E5%BF%AB%E9%80%92&expressNumbers=123456&communityName=%E9%95%BF%E5%9F%8E%E4%B8%80%E8%8A%B1%E5%9B%AD&receiveTime=5&Id=1231";
//Get("/Message/externalMerchantSendMessage?" + appParam, {}); var data = { "phone": "15914070649", "sign": "78a7ce797cf757916c2c7675b6865b54", "channel": "weijiakeji", "fetchCode": 1, "company": "%E9%A1%BA%E4%B8%B0%E5%BF%AB%E9%80%92", "Id": "1231" }; Post('/Message/externalMerchantSendMessage', data); }
//WebAPI Post方法 function Post(url, data) { $.ajax({ url: url, contentType:
"application/json", type: "POST", dataType: "json", async: true, cache: false, data: JSON.stringify(data), success: function (response) { $('#response').text(JSON.stringify(response)); }, error: function (XMLHttpRequest, textStatus, errorThrown) { alert(textStatus); } }); }; //// WebApi Get方法 function Get(url, data) { $.ajax({ url: url, contentType: "application/json", type: "GET", dataType: "json", async: true, cache: false, //data: JSON.stringify(data), success: function (response) { $('#response').text(JSON.stringify(response)); }, error: function (XMLHttpRequest, textStatus, errorThrown) { alert(textStatus); } }); };

    可見客戶傳遞的是一個鍵值對集合,就是一個JSON格式的對象。根據前面的代碼 bool isAuth = authModelBusiness.isAuth(param.channel, param.phone, param.sign);,可以分析出有三個參數是所有調用客戶都必須傳遞過來的,那就是:channel,phone,sign,而其它的參數就是簡訊模板的變數參數和參數值。那麼方法externalMerchantSendMessage(MessageContext param)中的參數就是一個可變對象。在C#4.0種存在一個dynamic不正是用來描述可變對象嗎?

那麼第一步修改傳入參數類型,之前是硬編碼的強類型MessageContext,現在不依賴此類,而是動態解析,修改externalMerchantSendMessage方法代碼如下:

                dynamic param = null;
                string json = Request.QueryString.ToString();

                if (Request.QueryString.Count != 0) //ajax get請求
                {
                    //相容舊的客戶調用寫法,暫時硬編了
                    if (json.Contains("param."))
                    {
                        json = json.Replace("param.", "");
                    }
                    json = "{" + json.Replace("=", ":'").Replace("&", "',") + "'}";
                }
                else  //ajax Post請求
                {
                    Request.InputStream.Position = 0; //切記這裡必須設置流的起始位置為0,否則無法讀取到數據
                    json = new StreamReader(Request.InputStream).ReadToEnd();
                }
                var serializer = new JavaScriptSerializer();
                serializer.RegisterConverters(new[] { new DynamicJsonConverter() });
                param = serializer.Deserialize(json, typeof(object));

DynamicJsonConverter的作用是將JSON字元串轉為Object對象,代碼如下:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Dynamic;
using System.Linq;
using System.Text;
using System.Web.Script.Serialization;

public sealed class DynamicJsonConverter : JavaScriptConverter
{
    public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
    {
        if (dictionary == null)
            throw new ArgumentNullException("dictionary");

        return type == typeof(object) ? new DynamicJsonObject(dictionary) : null;
    }

    public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override IEnumerable<Type> SupportedTypes
    {
        get { return new ReadOnlyCollection<Type>(new List<Type>(new[] { typeof(object) })); }
    }

    #region Nested type: DynamicJsonObject

    private sealed class DynamicJsonObject : DynamicObject
    {
        private readonly IDictionary<string, object> _dictionary;

        public DynamicJsonObject(IDictionary<string, object> dictionary)
        {
            if (dictionary == null)
                throw new ArgumentNullException("dictionary");
            _dictionary = dictionary;
        }

        public override string ToString()
        {
            var sb = new StringBuilder("{");
            ToString(sb);
            return sb.ToString();
        }

        private void ToString(StringBuilder sb)
        {
            var firstInDictionary = true;
            foreach (var pair in _dictionary)
            {
                if (!firstInDictionary)
                    sb.Append(",");
                firstInDictionary = false;
                var value = pair.Value;
                var name = pair.Key;
                if (value is string)
                {
                    sb.AppendFormat("{0}:\"{1}\"", name, value);
                }
                else if (value is IDictionary<string, object>)
                {
                    new DynamicJsonObject((IDictionary<string, object>)value).ToString(sb);
                }
                else if (value is ArrayList)
                {
                    sb.Append(name + ":[");
                    var firstInArray = true;
                    foreach (var arrayValue in (ArrayList)value)
                    {
                        if (!firstInArray)
                            sb.Append(",");
                        firstInArray = false;
                        if (arrayValue is IDictionary<string, object>)
                            new DynamicJsonObject((IDictionary<string, object>)arrayValue).ToString(sb);
                        else if (arrayValue is string)
                            sb.AppendFormat("\"{0}\"", arrayValue);
                        else
                            sb.AppendFormat("{0}", arrayValue);

                    }
                    sb.Append("]");
                }
                else
                {
                    sb.AppendFormat("{0}:{1}", name, value);
                }
            }
            sb.Append("}");
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            if (!_dictionary.TryGetValue(binder.Name, out result))
            {
                // return null to avoid exception.  caller can check for null this way...  
                result = null;
                return true;
            }

            var dictionary = result as IDictionary<string, object>;
            if (dictionary != null)
            {
                result = new DynamicJsonObject(dictionary);
                return true;
            }

            var arrayList = result as ArrayList;
            if (arrayList != null && arrayList.Count > 0)
            {
                if (arrayList[0] is IDictionary<string, object>)
                    result = new List<object>(arrayList.Cast<IDictionary<string, object>>().Select(x => new DynamicJsonObject(x)));
                else
                    result = new List<object>(arrayList.Cast<object>());
            }

            return true;
        }
    }

    #endregion
}
View Code

接下來是GetContent方法,此方法的目的很簡單,就是要根據客戶傳遞的模板變數參數鍵值對和簡訊模板內容,拼裝成最後的簡訊發送內容,之前此方法裡面是硬編碼的,現在我們需要變成動態獲取。

簡訊模板的內容示例:

【一應生活】您有一件單號為expressNumbers company,已到communityName收發室,請打開一應生活APP“收發室”獲取取件碼進行取件。點擊下載http://a.app.qq.com/o/simple.jsp?pkgname=com.ening.life

我發現這樣的模板內容有問題,模板中的變數參數是直接用的英文單詞表示的,而我們的簡訊內容中可能有時候也會存在英文單詞,那麼我就給所有的變數參數加上{}。修改後如下:

【一應生活】您有一件單號為{expressNumbers} {company},已到{communityName}收發室,請打開一應生活APP“收發室”獲取取件碼進行取件。點擊下載http://a.app.qq.com/o/simple.jsp?pkgname=com.ening.life

我們需要根據客戶傳遞過來的對象,將簡訊模板中的變數參數,替換成變數參數對應的值。那麼我們首先就要解析這個對象中的鍵值對信息。

        /// <summary>
        /// 把object對象的屬性反射獲取到字典列表中
        /// </summary>
        /// <param name="data">object對象</param>
        /// <returns>返回Dictionary(屬性名,屬性值)列表</returns>
         static Dictionary<string, string> GetProperties(object data)
        {
            Dictionary<string, string> dict = new Dictionary<string, string>();

            Type type = data.GetType();
            string[] propertyNames = type.GetProperties().Select(p => p.Name).ToArray();
            foreach (var prop in propertyNames)
            {
                object propValue = type.GetProperty(prop).GetValue(data, null);
                string value = (propValue != null) ? propValue.ToString() : "";
                if (!dict.ContainsKey(prop))
                {
                    dict.Add(prop, value);
                }
            }
            return dict;
        }

接下來是通過正則表達式來匹配簡訊模板內容。

        /// <summary>
        /// 多個匹配內容
        /// </summary>
        /// <param name="sInput">輸入內容</param>
        /// <param name="sRegex">表達式字元串</param>
        /// <param name="sGroupName">分組名, ""代表不分組</param>
        static List<string> GetList(string sInput, string sRegex, string sGroupName)
        {
            List<string> list = new List<string>();
            Regex re = new Regex(sRegex, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline);
            MatchCollection mcs = re.Matches(sInput);
            foreach (Match mc in mcs)
            {
                if (sGroupName != "")
                {
                    list.Add(mc.Groups[sGroupName].Value);
                }
                else
                {
                    list.Add(mc.Value);
                }
            }
            return list;
        }
        public static string ReplaceTemplate(string template, object data)
        {
            var regex = @"\{(?<name>.*?)\}";
            List<string> itemList = GetList(template, regex, "name"); //獲取模板變數對象

            Dictionary<string, string> dict = GetProperties(data);
            foreach (string item in itemList)
            {
                //如果屬性存在,則替換模板,並修改模板值
                if (dict.ContainsKey(item))
                {
                    template = template.Replace("{"+item+"}", dict.First(x => x.Key == item).Value);
                }
            }

            return template;
        }

這樣就講客戶傳遞的對象和我們的解析代碼進行瞭解耦,客戶傳遞的對象不再依賴於我們的代碼實現,而是依賴於我們數據表中模板內容的配置。

這幾個方法我是寫好了,順便弄個單元測試來驗證一下是不是我要的效果,可憐的是,這個項目中根本就沒用到單元測試,沒辦法,我自己創建一個單元測試

    [TestClass]
    public class MatchHelperTest
    {
        [TestMethod]
        public void ReplaceTemplate()
        {
            //模板文本
            var template = "【一應生活】您有一件單號為{expressNumbers} {company},已到{communityName}收發室,請打開一應生活APP“收發室”獲取取件碼進行取件。點擊下載http://a.app.qq.com/o/simple.jsp?pkgname=com.ening.life";
            //數據對象
            var data = new { expressNumbers = "2016", company = "長城", communityName = "長怡花園"};
            string str = "【一應生活】您有一件單號為2016 長城,已到長怡花園收發室,請打開一應生活APP“收發室”獲取取件碼進行取件。點擊下載http://a.app.qq.com/o/simple.jsp?pkgname=com.ening.life";
            string str1=MatchHelper.ReplaceTemplate(template, data);

            Assert.AreEqual(str1,str);

            //重覆標簽的測試
            template = "【一應生活】您有一件單號為{expressNumbers} {company},已到{communityName}收發室,單號:{expressNumbers}";
            str = "【一應生活】您有一件單號為2016 長城,已到長怡花園收發室,單號:2016";
            str1=MatchHelper.ReplaceTemplate(template, data);
            Assert.AreEqual(str1, str);
        }
    }

說到單元測試,我相信在許多公司都沒有用起來,理由太多。我也覺得如果業務簡單的話,根本沒必要寫單元測試,國內太多創業型公司項目進度都非常趕,如果說寫單元測試不費時間,那絕對是騙人的,至於說寫單元測試能提高開發效率,減少返工率,個人感覺這個還真難說,因為即便不寫單元測試也還是

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

-Advertisement-
Play Games
更多相關文章
  • 什麼是負載均衡 負載均衡(Load Balance)是分散式系統架構設計中必須考慮的因素之一,它通常是指,將請求/數據【均勻】分攤到多個操作單元上執行,負載均衡的關鍵在於【均勻】。 常見的負載均衡方案 常見互聯網分散式架構如上,分為客戶端層、反向代理nginx層、站點層、服務層、數據層。可以看到,每 ...
  • 回到目錄 什麼是LindAspects 之前寫了關於Aspects的文章《Lind.DDD.Aspects通過Plugins實現方法的動態攔截~Lind里的AOP》,今天主要在設計思想上進行刨析一下,對緩存攔截器一直沒有實現,所以文章了也一直沒有發出來,讓大家等這麼久實在不好意思。LindAspec ...
  • 一、概述 橋接模式:將兩個原本不相關的類結合在一起,然後利用兩個類中的方法和屬性,輸出一份新的結果。 二、案例 1、模擬毛筆(轉) 需求:現在需要準備三種粗細(大中小),並且有五種顏色的比 如果使用蠟筆,我們需要準備3*5=15支蠟筆,也就是說必須準備15個具體的蠟筆類。而如果使用毛筆的話,只需要3 ...
  • 第一是:項目的路徑需要放在Documents and Settings\,也就是預設的文件夾的地方,不然會報錯錯誤範例為:Question:CY8CKIT-023 kit example project fails to build in PSoC Creator with the followin ...
  • 我使用的是海康DS-2CD852MF-E, 200萬,網路攝像機,已經比較老了,不過SDK在海康官網下載的,開發流程都差不多. 海康攝像機回調解碼後的視頻數據格式為YV12,順便說一下YV12的數據格式 YYYY V U. 我這個是720P,即1280 * 720解析度. 那麼Y分量的數量為 128 ...
  • 適配器模式:將一個類的介面轉換成客戶希望的另外一個介面,使得原本由於介面不相容而不能一起工作的那些類可以在一起工作。 如下圖(借圖): // 設置書的介面 客戶端測試: 輸出結果: 這時候,你想創建一個可以復用的類,該類可以與其他不相關的類或不可預見的類(即那些介面可能不一定相容的類)協同工作。 如 ...
  • 這節主要說一下Angular的指令。Angular的指令有內置指令和自定義指令。 一、內置指令 在 Angular 中通過指令對DOM的功能進行擴展。這也是對常用功能的模塊化封裝。Angular 提供了一系列常用的指令,這些指定都是以 ng 開頭的,我們稱為內置指令。 後面會講到如何自定義指令。其實 ...
  • TYPESDK 服務端設計思路與架構之一:應用場景分析 作為一個渠道SDK統一接入框架,TYPESDK從一開始,所面對的需求場景就是多款游戲,通過一個統一的SDK服務端,能夠同時接入幾十個甚至幾百個各種渠道的SDK。而且這些渠道介面的具體接入欄位和接入邏輯,每個月以至每周,都可能發生或大或小的變動。 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...