星期日, 4月 23, 2023

[Azure Pipeline] 秘密變數

在討論內看見 Azure pipeline 內要使用 Token 時,要使用安全變數來保護,查資料學習並筆記,增加 pipleline 了解,該筆記以主分支上有異動要發通知來練習,只記錄秘密變數使用相關

秘密變數官方文件說明
Secret variables are encrypted variables that you can use in pipelines without exposing their value. Secret variables can be used for private information like passwords, IDs, and other identifying data that you wouldn't want to have exposed in a pipeline. Secret variables are encrypted at rest with a 2048-bit RSA key and are available on the agent for tasks and scripts to use.
開一個 Empty pipeline 範本,裡面只有一個 PowerShell Task

[Azure] 秘密變數-0

Variables Tab 內新增 SecretVar 變數並輸入 Token (截圖內用 123 來表示),重點是要點選下圖鎖頭圖示,閉合即代表秘密變數

[Azure] 秘密變數-1

Task  Tab 內 Environment Variables 內設定使用 SecretVar 秘密變數,設定結果如下圖

[Azure] 秘密變數-2

Triggers Tab 內啟用 Enable continuous integtation 選項,只要 master 有變化就會觸發

[Azure] 秘密變數-3

Line Notify 相關設定

[Azure] 秘密變數-4

以上就完成設定,實際執行就會收到通知

[Azure] 秘密變數-5

星期四, 4月 20, 2023

[Shopify] 根據市場指定物流方式

原以為單一商城只能有單一物流,測試後發現可以根據市場指定物流方式

兩個市場分別為台灣和美國

[Shopify] 根據市場指定物流方式-1

運送和配送部分,分別設定,該筆記內就直接定死價格,台灣免費、美國運費 1,500 元

[Shopify] 根據市場指定物流方式-2

結帳時填寫地址時,運送地址就會出現台灣和美國兩個選項,選擇台灣會出現免費,選擇美國會跳出 1,500 元
[Shopify] 根據市場指定物流方式-3

星期三, 4月 19, 2023

[RV] 新聞稿樣式報表 - 分頁

經歷這兩篇筆記,發現在 SSRS 內要設計多欄位報表進行分頁,似乎是不可行
轉念從 C# 程式 Code 來處理分頁需求,作法很單純就是把針對群組跑迴圈,一個群組就送一次報表,每次都直接輸出不預覽,SSRS 多欄位報表內就不用特別設計,拉一個 Tablix 在多欄位報表內顯示資料而已,但該作法缺點在於
  • 直接輸出,沒有辦法預覽:真的需要預覽功能的話,需要拉一個用頁面來顯示相關資料,讓使用者先行確認,確認後再進行列印
  • 無法使用 SSRS 內建頁碼功能:實務上剛好也沒有這個需求,就沒有多加研究,暫時 pass 囉
直接輸出報表,請參考該筆記 - [RV] 列印本機報表而不進行預覽

以 AdventureWorks2019 Person.Address Table 資料為主,針對程式跑迴圈並直接輸出報表至印表機
namespace MultiColumnPageBreak
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            var dbContext = new AdventureWorks2019DbContext();

            var source = dbContext.Address
                 .Where(x => x.City == "Miami" || x.City == "Snohomish")
                .OrderBy(x => x.City)
                .ThenBy(x => x.AddressLine1)
                .ToList();

            var reportDataSource = new ReportDataSource(nameof(Address));
            reportViewer1.LocalReport.DataSources.Add(reportDataSource);

            var cities = source.GroupBy(x => x.City).Select(x => x.Key).ToList();
            foreach (string city in cities)
            {
                var citySource = source.Where(x => x.City == city).ToList();
                reportDataSource.Value = citySource;

                // TODO 報表直接輸出
            }
        }
    }
}

正常多欄位報表

   [RV] 新聞稿樣式報表 - 分頁-1

群組跑迴圈直接輸出印表機,下面截圖還是透過 SSRS 預覽來呈現的,可以呈現效果就行

[RV] 新聞稿樣式報表 - 分頁-2

[RV] 新聞稿樣式報表 - 分頁-3

星期二, 4月 18, 2023

[SSRS] 新聞稿樣式報表 - 子報表

在 [SSRS] 新聞稿樣式報表 - 分欄 內確定沒有辦法達到分頁功能後,想到可以透過清單 (List) 搭配子報表 (Subreport) 來嘗試看看,發現多欄位設定在子報表內沒有辦法一欄接續一欄呈現,會變成一欄一頁,EX:報表一頁有兩欄,但在子報表內會變成一頁一欄,總共兩頁

以 AdventureWorks2019 Person.Address Table 資料為主,把同一個城市內的地址群組在一起來紀錄,子報表設計可以參考這三篇筆記
以下就單純文字紀錄而已

資料來源,只抓取一個城市資料來顯示,資料量可以呈現一頁兩欄效果就好

namespace MultiColumnSubreport
{
    // 故意弄兩個 class 來呈現資料,不要都用 Address
    public class MasterReportModel
    {
        public string City { get; set; }
    }
    
    public class DetailReportModel
    {
        public string City { get; set; }

        public string Address { get; set; }
    }
    

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            var dbContext = new AdventureWorks2019Entities();

            var source = dbContext.Address
                .Where(x => x.City == "Perth")
                .OrderBy(x => x.City)
                .ThenBy(x => x.AddressLine1)
                .ToList();

            var masterSource = source.GroupBy(g => g.City).Select(g => new MasterReportModel() { City = g.Key }).ToList();
            reportViewer1.LocalReport.DataSources.Add(new ReportDataSource(nameof(MasterReportModel), masterSource));

            reportViewer1.LocalReport.SubreportProcessing += (senderInner, eInner) =>
            {
                string city = eInner.Parameters[nameof(DetailReportModel.City)].Values[0]?.ToString();

                var detailSource = source
                .Where(w => w.City == city)
                .Select(s => new DetailReportModel()
                {
                    City = s.City,
                    Address = s.AddressLine1
                })
                .OrderBy(x => x.Address)
                .ToList();

                eInner.DataSources.Add(new ReportDataSource(nameof(DetailReportModel), detailSource));
            };

            reportViewer1.SetDisplayMode(DisplayMode.PrintLayout);

            reportViewer1.RefreshReport();
        }
    }    
}
正常的一頁兩欄位的多欄位報表

[SSRS] 新聞稿樣式報表 - 子報表-1

清單搭配子報表來呈現多欄位,變成一頁一欄,總共兩頁

   [SSRS] 新聞稿樣式報表 - 子報表-2

[SSRS] 新聞稿樣式報表 - 子報表-3

原本擔心多欄位設計在單一群組有第二頁情況下,子報表無法呈現,一整個多慮,連正常多欄效果都出不來,完全無法使用

星期日, 4月 16, 2023

[SSRS] 新聞稿樣式報表 - 分欄

新聞稿樣式報表,俗稱多欄位報表,之前沒有使用到分頁功能,實際操作發現在該情況下設定群組分頁,稱呼為分欄功能會比較貼切

以 AdventureWorks2019 Person.Address Table 資料為主,把同一個城市內的地址群組在一起來紀錄,請參考該筆記 - 新聞稿樣式報表 來建立報表,以下只記錄分頁相關重點而已

資料來源,只抓取兩個城市資料來顯示,方便閱讀看出效果就好
namespace MultiColumnPageBreak
{
    public partial class Form1 : Form
    {


        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            var dbContext = new AdventureWorks2019Entities();
            var source = dbContext.Address
                .Where(x => x.City == "Miami" || x.City == "Snohomish")
                .OrderBy(x => x.City)
                .ThenBy(x => x.AddressLine1)
                .ToList();

            reportViewer1.LocalReport.DataSources.Add(new ReportDataSource("DataSet1", source));

            // 新聞稿樣式報表,在預覽結果時,要使用 "整頁模式" 才可以看見效果喔
            reportViewer1.SetDisplayMode(DisplayMode.PrintLayout);

            reportViewer1.RefreshReport();
        }
    }
}

多欄位報表,欄位數量設定為 2

[SSRS] 新聞稿樣式報表 - 分頁-4

未設定群組分頁,全部資料都在第一欄位

[SSRS] 新聞稿樣式報表 - 分頁-1

設定群組分頁

[SSRS] 新聞稿樣式報表 - 分頁-2

設定群組分頁後的分欄效果,一個群組一欄,紅線是頁碼顯示,分頁後還是只有一頁而已

[SSRS] 新聞稿樣式報表 - 分頁-3

SSRS 內多欄位報表似乎沒有真正的分頁功能,Orz

星期五, 4月 14, 2023

[C#] Excel - 儲存為 xls 格式

把該篇 VFP 筆記 - [VFP] Automation 應用 - 儲存為 xls 格式 改成透過 C# 把 xlsx 轉成 xls

該篇就完全看 Code 理解囉
using Excel = Microsoft.Office.Interop.Excel;

namespace Utilities
{
    public static class ExcelHelper
    {
        public static void XLSConvert(
            string sourceFileFullName,
            string destFileFullName)
        {

            Excel.Application excel = null;
            Excel.Workbook workBook = null;

            try
            {
                excel = new Excel.Application();

                // 萬一目的地已經有同名檔案,不會出現詢問視窗
                excel.DisplayAlerts = false;

                // 開啟 xlsx 檔案
                workBook = excel.Workbooks.Open(sourceFileFullName);

                // Excel.XlFileFormat.xlExcel8 會產生 Excel97-2003 的 xls 格式
                workBook.SaveAs(destFileFullName, Excel.XlFileFormat.xlExcel8);

                // 關閉工作簿並退出 Excel 應用程式
                workBook.Close();
                excel.Quit();

            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                // 釋放 Excel COM 物件
                if (workBook != null)
                    Marshal.ReleaseComObject(workBook);

                if (excel != null)
                    Marshal.ReleaseComObject(excel);
            }
        }
    }
}
實務上第一次操作 Microsoft.Office.Interop.Excel,要特別注意主動釋放 Unmanaged Excel COM 物件,要不然工作管理員內有會有一堆 Excel 殘留在系統內

星期四, 4月 13, 2023

[DP] 建造者模式 - Fluent

[DP] 建造者模式 文章內容換成 Fluent 寫法來記錄,使用 Fluent 寫法可以增加流程順序可讀性

IPersonFluentBuilder

每個 Interface 都必須回傳自己本身
namespace Blog_Builder
{
    public interface IPersonFluentBuilder
    {
        IPersonFluentBuilder BuildHead();
        IPersonFluentBuilder BuildBody();
        IPersonFluentBuilder BuildArmLeft();
        IPersonFluentBuilder BuildArmRight();
        IPersonFluentBuilder BuildLegLeft();
        IPersonFluentBuilder BuildLegRight();
        IPersonFluentBuilder Build();
    }
}
PersonThinFluentBuilder
using System.Drawing;

namespace Blog_Builder
{
    public class PersonThinFluentBuilder : IPersonFluentBuilder
    {
        private Graphics _g;
        private Pen _p;

        public PersonThinFluentBuilder(
            Graphics g,
            Pen p)
        {
            _g = g;
            _p = p;
        }

        public IPersonFluentBuilder BuildHead()
        {
            _g.DrawEllipse(_p, 50, 20, 30, 30);
            return this;
        }

        public IPersonFluentBuilder BuildBody()
        {
            _g.DrawRectangle(_p, 60, 50, 10, 50);
            return this;
        }

        public IPersonFluentBuilder BuildArmLeft()
        {
            _g.DrawLine(_p, 60, 50, 40, 100);
            return this;
        }

        public IPersonFluentBuilder BuildArmRight()
        {
            _g.DrawLine(_p, 70, 50, 90, 100);
            return this;
        }

        public IPersonFluentBuilder BuildLegLeft()
        {
            _g.DrawLine(_p, 60, 100, 45, 150);
            return this;
        }

        public IPersonFluentBuilder BuildLegRight()
        {
            _g.DrawLine(_p, 70, 100, 85, 150);
            return this;
        }

        public IPersonFluentBuilder Build()
        {
            return this;
        }
    }
}

PersonFluentDirector
namespace Blog_Builder
{
    public class PersonFluentDirector
    {
        private IPersonFluentBuilder _pb;

        public PersonFluentDirector(IPersonFluentBuilder personFluentBuilder)
        {
            _pb = personFluentBuilder;
        }

        public void CreatePerson()
        {
            _pb
                .BuildHead()
                .BuildBody()
                .BuildArmLeft()
                .BuildArmRight()
                .BuildLegLeft()
                .BuildLegRight()
                .Build();
        }
    }
}
實際使用
using System.Drawing;
using System.Windows.Forms;

namespace Blog_Builder
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void pictureBox1_Paint(object sender, PaintEventArgs e)
        {
            // 測試一:使用 Director 來決定順序
            Pen p_LightGreen = new Pen(Color.LightGreen);
            var tfb = new PersonThinFluentBuilder(e.Graphics, p_LightGreen);
            var pd_ThinFluent = new PersonFluentDirector(tfb);
            pd_ThinFluent.CreatePerson();

            // 測試二:使用 Builder 決定順序
            Pen p_LightPink = new Pen(Color.LightPink);
            new PersonThinFluentBuilder(e.Graphics, p_LightPink)
                .BuildHead()
                .BuildBody()
                .BuildArmLeft()
                .BuildArmRight()
                .BuildLegLeft()
                .BuildLegRight()
                .Build();
        }
    }
} 
 
之前看到 Fluent 介紹時,因為一直點擊選擇 Method 來決定建構流程,以為是類似擴充方法設計,學習後發現是透過回傳本身來做到

星期二, 4月 11, 2023

[C#] AggregateException

AggregateException 代表應用程式執行期間所發生的一或多個錯誤,就直接看範例來理解

範例一:收集連續不中斷流程中的 Exception

AggregateException.ToString() 可以列出全部 Exception
namespace AggregateExceptionSample
{
    internal class Program
    {
        static void Main(string[] args)
        {
            ProcessCollection();
        }

        static void ProcessCollection()
        {
            string number = "1234567890";

            Action<string> a1 = s => throw new Exception(nameof(s));
            Action<string> a2 = s => throw new DivideByZeroException(nameof(s));
            Action<string> a3 = s => throw new NotSupportedException(nameof(s));

            var methods = new List<Action<string>>()
            {
                a1 ,
                a2 ,
                a3
            };

            var exceptions = new List<Exception>();
            foreach (var method in methods)
            {
                try
                {
                    method(number);
                }
                catch (Exception ex)
                {
                    exceptions.Add(ex);
                }
            }

            if (exceptions.Any())
            {
                try
                {
                    throw new AggregateException("發生錯誤", exceptions);
                }
                // 使用 ToString() 列出上述三個 Exception
                catch (AggregateException ex)
                {
                    Console.WriteLine("顯示全部的 Exception 訊息");
                    Console.WriteLine("-------------------------");
                    Console.WriteLine(ex.ToString());
                }
            }
        }
    }
}

[C#] AggregateException-1

範例二:Flatten 方法官方文章範例

該範例在一個 Task 內發動子 Task 來蒐集 Exception,使用 C# 6.0 Exception Filter 來修正原範例 foreach Exception 判斷
namespace AggregateExceptionSample
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Flatten();
        }

        static void Flatten()
        {
            var task1 = Task.Factory.StartNew(() =>
            {
                var child1 = Task.Factory.StartNew(() =>
                {
                    var child2 = Task.Factory.StartNew(() =>
                    {
                        // This exception is nested inside three AggregateExceptions.
                        throw new CustomException("Attached child2 faulted.");
                    }, TaskCreationOptions.AttachedToParent);

                    // This exception is nested inside two AggregateExceptions.
                    throw new CustomException("Attached child1 faulted.");
                }, TaskCreationOptions.AttachedToParent);
            });

            try
            {
                task1.Wait();
            }
            // 使用 C# 6.0 Exception Filter 來篩選 Exception
            catch (AggregateException ae) when
            (
                ae
                .Flatten()
                .InnerExceptions
                .Any(e => e.GetType() == typeof(CustomException))
            )
            {
                Console.WriteLine(ae.Message);
            }
        }
    }

    public class CustomException : Exception
    {
        public CustomException(string message) : base(message)
        {

        }
    }
}
[C#] AggregateException-2

星期三, 4月 05, 2023

[C#] Binding - dataMember 參數

在 Line 社群看到的問題,錯誤訊息就很神奇,因為沒有使用 Binding 綁定 public field 過

[C#] Binding - dataMember 參數-1

在官方文件 Binding Constructor 有提到 dataMember 只能綁定 property 和 list 而已

[C#] Binding - dataMember 參數-2
C# 測試 Code
namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        Demo demo = new Demo();

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Binding binding = new Binding(
                nameof(TextBox.Text),
                demo,
                nameof(demo.demoField),
                true,
                DataSourceUpdateMode.OnValidation);

            textBox1.DataBindings.Add(binding);
        }
    }

    public class Demo
    {
        public string demoField = string.Empty;
    }
}
把 binding dataMember 對象修正為 property 就不會拋出該 Exception