Design

Design
asp.net mvc
顯示具有 C# 標籤的文章。 顯示所有文章
顯示具有 C# 標籤的文章。 顯示所有文章

2019年5月6日 星期一

ASP.NET MVC 三層架構Three tier 實作

滿久之前有跟大家提三到層架構,回顧可以先點這裡,卻一直少了實作的部分,這範例專案其實三年前就做好了只是一直放著沒時間補上,這架構其實也算很古老了,今天就把這部分補上.

這邊的範例就是簡單的identity登入功能 透過三層架構的方式
順序從前端輸入登入  打Controller層自己建的登入WabApi方法 再到Service層驗證登入的邏輯  最後在Repository層跟DB溝通 完整降低耦合 邏輯切割 邏輯也只須關注Service層 不管在系統開發業務切割都有很大的好處.首先我們先看下面這張專案圖
當然以上不是一定的,純粹給大家一個基本三層的建構概念體驗當中的魅力
首先我們要進行分層我們需要熟悉一種技術Dependency injection(DI 依賴注入) 
跟Interface(https://aspnetmars.blogspot.com/2017/05/interface-c.html)透過介面的方式實作,這也都算滿老的基礎技術網路上也滿多文章的,這有時間再整理一篇,我們繼續.


首先我們需要使用先nuget我們的di套件這邊使用Unity(https://aspnetmars.blogspot.com/2017/05/unity-di.html),也不一定要用unity也有其他種大同小異
有兩種MVC跟WebAPI的版本 我這邊是兩個都載 差別就是一個是Controller用跟WebAPI用.其實也只要用WebApi,MVC版是看你們有沒要在Controller的地方注入


然後官方也有提醒記得在Global.asax加入以下這段
然後可以發現我們App_Start多了兩個檔案UnityConfig.cs跟UnityMvcActivator.cs 
我們重點放在UnityConfig.cs注入的相關參數都會在這執行,UnityMvcActivator基本上不太會動到主要功用就是程序剛執行要跑的部分


相關可以發現我依照名稱尾端名稱分別注入了Service跟Repository其實簡略點可以只注入Service
然後FormAllInterfaces自動就去找Interface完成我們的注入
然後可以發現我們多了個getInjectionMembers底下實作了LoggingInterceptionBehavior
主要功用為去攔截我們每個注入的方法,這好處是什麼.你應該也猜到了.就是可以記錄我們的log這邊我是使用了Nlog(https://aspnetmars.blogspot.com/2017/05/nlog-bug.html) + Slack再來就跟各位演示整個流程

還沒忘記我們一開始的功能吧 開始登入網站 看看這架構的魅力吧

1.首先登入這沒問題



















2.再來就是打API來到我們Controller層 可以發現我們這邊又去呼叫Service層
































3.來到Service層,這邊又去呼叫我們Repository層要資料 這邊就是可以寫我們商業邏輯的地方,可以發現我們吧一些驗證邏輯寫在這如果我們邏輯需要異動只要改這個部分也不會影響到Controller 做到業務分層,在共同開發下也更為清楚,好處可以說是非常多,特別是在東西越來越多的狀況下越能體會到這架構的好處






























4.來到Repository層 這邊就是可以去我們資料庫要資料了 像我們每一層都需要定義Model分層的話又可以讓每個階層的東西放在各自位置這也是好處之一.























5.最後在Repository > Service > Controller  當然就是登入成功啦


然後剛剛有跟各位提到LoggingInterceptionBehavior
我這邊自己寫的 基本上是長這樣  紀錄 呼叫 , 回傳 或 Exception 這邊紀錄是使用Nlog我也有寫過一篇大家可以去看看,然後我範例記錄位置我是記錄在Slack 因為那時候Slack很潮XD 

Slack就會長這樣 完整記錄有注入過的方法的資訊 紀錄格式就不一定看各位想紀錄什麼

錯誤的話就會像這樣 我在哪一層哪個方法錯誤


以上就大概是簡單的範例  
架構上要說簡單也簡單 要說繁雜就是繁雜  後續還要考慮單元測試 自動部屬 等等等  還有很多可以在說明跟優化,有任何問題可以在底下留言.

2019年1月30日 星期三

C#基礎 關於float、double、decimal 浮點計算精度問題

C#中關於浮點計算 float、double、decimal  有這三中 實數型可以使用
但是我們都知道使用float、double會有小數點計算不準確的情況
特別是跟錢有關係的最好都使用高精準度decimal,知所以會叫高精準度就代表也有精度損失的時候,不過跟比float、double算是商業上夠用了等等下面會舉個例子

我們先來看小數點計算精度損失的例子
可以發現我們float、double計算都出現精度損失的問題這種問題如果不理會
後續會造成很大的嚴重後果 所以最好都使用decimal



當然我剛有提到decimal雖然擁有高精度但是就代表也會有精度損失的問題下面就舉個例子
可以看到我們的結果不是我們要的10000000000000000000000000000.1
不過這是比較極端的例子 正常也不會到這麼大的位數



再來我們再進行比對時資料結構一些問題 往往會需要先toString()在進行比對
可以發現我們float、double都是 true 原因是轉為字串float、double會自動把小數點後的零去除
但是卻發現decimal是false  原因是轉為字串 小數點後的零被保存下來
這時我們需要去除小數點後的零  stackoverflow 上的大神 有一招很酷的方法
就是我們的decimal 除掉 1.000000000000000000000000000000000
就會神奇發現我們小數點後的零都不見了 這時我們就可以擴充成方法作為使用  在進行比對我們就可以解決上述的問題



以上是C# 浮點精度 需要注意的事項  還是乖乖用decimal吧  雖然這是small tips不過還是開發時不可不注意的東西,以下是程式碼



namespace 練習
{
    class Program
    {
        static void Main(string[] args)
        {

            //float
            float f = 1.1f;
            f += 1.1f;

            //double
            double d = 1.1d;
            d += 1.1d;

            //decimal
            decimal de = 1.1m;
            de += 1.1m;

            Console.WriteLine("{0:G50}", f);
            Console.WriteLine("{0:G50}", d);
            Console.WriteLine("{0:G50}", de);


            //decimal 精度損失的例子
            decimal dd = 10000000000000000000000000000m;
            dd += 0.1m;
            Console.WriteLine("{0:G50}", dd);

            //float 比對
            float f1 = 1.1f;
            float f2 = 1.100f;
            Console.WriteLine(f1.ToString() == f2.ToString());

            //decimal 比對
            double d1 = 1.1d;
            double d2 = 1.100d;
            Console.WriteLine(d1.ToString() == d2.ToString());

            //decimal 比對
            decimal de1 = 1.1m;
            decimal de2 = 1.100m;
            Console.WriteLine(de1.ToString() == de2.ToString());

            decimal Money1 = 1.1m;
            decimal Money2 = 1.100m;

            //去除小數點後的零 方便作比對用
            Console.WriteLine(Money1.Streamline().ToString() == Money2.Streamline().ToString());


            Console.Read();
        }

    }


    
}

namespace CustomExtensions
{
    public static class Extension
    {
        public static decimal Streamline(this decimal d)
        {
            return d / 1.000000000000000000000000000000000m;
        }
    }
}

2017年5月27日 星期六

Nlog trace code 追蹤你的code

Nlog?

一個非常好用的code trace library 怎樣好用呢
往往我們專案Debug 會去需要看我們的錯誤 localhost Deubg 當然沒什麼問題
但是如果我們網站已經上線或是已經在server的測試階段,我們後端引發前端的錯誤
你很難依照當前頁面去判斷,而且很常就是我localhost 沒問題啊 怎上server 就有問題.
這時候我們就需要去在我們的程式碼加上記錄錯誤訊息的方法 這時候就需要用到我們的nlog
不只記錄exception,你也可以當作操作紀錄去分析client端的操作.
而且我們不只本機可以記錄 還可以連接我們的db 或是連接例如slack,exceptionless

2017年5月19日 星期五

Unity DI 注入方法


底下為較常用到的Unity注入方法 單一介面
container.RegisterType<ICookService, CookService>();

如果同一介面下 有很多不一樣的Service 只需要後面給他個名稱就好了 用以辨識實作時用
如本篇interface
container.RegisterType<ICookService, FoodService>("FoodService");
container.RegisterType<ICookService, DrinkService>("DrinkService");

當我們實作時需要 後面也是一樣要加上要Resolve的service名稱 這
樣他才知道我是要實作ICookService介面的FoodService 不然他會FoodService DrinkService傻傻分不清楚
var service = container.Resolve("FoodService");
還有非常炫砲的寫法 自動搜尋所有class跟Interface 做注入
            container.RegisterTypes(
            //針對所有class Service名稱後有做注入
            AllClasses.FromLoadedAssemblies()
           .Where(t => t.Name.EndsWith("Service")),
            //搜尋所有Interface做Mapping
            WithMappings.FromAllInterfaces,
            //可以定義註冊名稱
            WithName.Default,
            //指定註冊的生命週期 說明
            WithLifetime.ContainerControlled);

Interface C#

以往我們寫code總是會這樣 new一個新的物件 直接依賴但是當我們在較複雜的架構時,我們就需要一直去重構方法
 
class Program
{
    static void Main(string[] args)
    {
         Dosomething do = new Dosomething();
          do.Write("Hello World");
    }
}
  
class Dosomething
{
    public void Write(string msg)
    {
        Console.WriteLine(msg);
    }
}

後續衍伸出interface 定義個實作的介面 我們是先定義好實作 後續才是去針對判斷去new 出我們想實作的辦法 這樣好處是程式更有規範更方便理解,統一 一個接口方法最後看你想實作哪個部分
 
  class Program
    {
       
        static void Main(string[] args)
        {
            ICooking cooking;
            //我決定要做三杯雞 來吧!
            var food = "三杯雞";
            if (food == "三杯雞")
            {
                cooking = new FoodService();
                Console.WriteLine(cooking.Make());
            }
            else {
                cooking = new DrinkService();
                Console.WriteLine(cooking.Make());
            }
            Console.Read();
        }

    }

    interface ICooking
    {
        string Make();
    }

    class FoodService : ICooking
    {
        public string Make()
        {

            return "紅燒牛肉";
        }
    }

    class DrinkService : ICooking
    {
        public string Make()
        {
            return "珍珠奶茶";
        }
    }

2017年5月18日 星期四

.NET SignalR 初級引用

What is SignalR?

相信你會看到我的文章也有大概想做的事情了

ASP.NET SignalR是一個用於ASP.NET開發人員的library ,簡化了web 應用程序的功能
讓Server跟Client 達到 Real-Time 的效果。

你問我什麼是Real-Time?
簡單講就是類似聊天室的功能,讓網頁上的資訊"即時"達到跟server溝通的橋樑
當然不只聊天功能,能用的應用包含想即時的告訴client做某些指定動作。

WebSockets是HTML5提供的新的API,可以在Web網頁與服務器端間建立Socket連接,當WebSockets可用時(即瀏覽器支持Html5)SignalR使用WebSockets,當不支持時SignalR將使用其它技術來保證達到相同效果。

SignalR當然也提供了非常簡單易用的高階API,使服務器端可以單個或批量調用客戶端上的JavaScript函數,並且非常方便地進行連接管理,例如客戶端連接到服務器端,或斷開連接,客戶端分組,以及客戶端授權,使用SignalR都非常容易實現



使用SignalR調用方法

就讓我們開始吧!
我們就來做個雙向的聊天室吧 澳門首家線上聊天室上線拉

NuGet 確保底下兩個已先安裝
1.Microsoft.AspNet.SignalR.Core
2.Microsoft.AspNet.SignalR.JS


我們先建立個  Startup.cs
註冊SignalR middleware
主要用於建立Client端連接到Hub的路由 在我們應用程序調用時執行我們的MapSignalR的擴充方法
 
using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(MyApplication.Startup))]
namespace MyApplication
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            // Any connection or hub wire up and configuration should go here
            app.MapSignalR();
        }

    }
}


然後我們建個HUB的Class 名子就叫ServiceHub.cs吧 如下我們建個SendMessage的方法 然後傳給所有人
 [HubMethodName("")]的作用是如果沒加這段的話 我們javascirpt上的都要以小寫開頭因為我們有加了所以是依照我們定義的名稱就好
 
[HubMethodName("Hub")]
 public class ServiceHub :Hub
    {

        [HubMethodName("SendMessage")]
        public void SendMessage(string msg) {

            Clients.All.addNewMessageToPage(msg);
        }
    }


然後我們頁面上該怎麼接收訊息呢 我們先在這個頁面上建立個click 傳送去呼叫SendMessage這個方法 然後戴上我們定義好的參數 contosoChatHubProxy.on 也監聽addNewMessageToPage Server.cshtml
 

<script src="~/Scripts/jquery-1.10.2.min.js">
<script src="~/Scripts/bootstrap.min.js">
<script src="~/Scripts/jquery.signalR-2.2.2.js">

    var connection = $.hubConnection();
    var contosoChatHubProxy = connection.createHubProxy('serviceHub');
    contosoChatHubProxy.on('addNewMessageToPage', function (message) {
        console.log(message);
    });
    connection.start()
        .done(function () {
     
    
        })
        .fail(function () { console.log('Could not connect'); });



    $('#add').click(function () {
        contosoChatHubProxy.invoke('SendMessage', $('#message').val());
        $('#message').val('').focus();
    });

Client.cshtml 也是一樣作法 監聽 建立連線
 

<script src="~/Scripts/jquery-1.10.2.min.js"&gt
<script src="~/Scripts/bootstrap.min.js">
<script src="~/Scripts/jquery.signalR-2.2.2.js">

    var connection = $.hubConnection();
    var contosoChatHubProxy = connection.createHubProxy('serviceHub');
    contosoChatHubProxy.on('addNewMessageToPage', function (message) {
        console.log(message);
    });
    connection.start()
        .done(function () {
     
    
        })
        .fail(function () { console.log('Could not connect'); });


當我server 這邊送出訊息後 可以發現 兩邊的console.log 都會出現一樣的訊息
當然我們可以客製化的去顯示兩邊不同的內容訊息

 進階的做法可以 創建不同Groups 傳送訊息給指定的Group 或是 單獨的對象
還可以SignalR Scaleout 配合 Redis or SQL Server 加快傳送訊息的速度
很屌的是還可以建立個獨立的SignalR Self-Host 好處是就不用依賴於IIS上面 
我只要啟動應用程序 我整段系統就打開了
中間還包括authentication 跟一些防範漏訓的機制
 以上部分有時間再補上

2016年4月20日 星期三

C# 問號的意思

1. int? number;
2. txtName.Text.Trim().Length == 0 ? null : txtName.Text.Trim()
3 .a??0

第一種的意思
?(單問號),是System.Nullable<T>的縮寫形式,可null類型。

第二種的意思
? 為三元運算符,在上面例子中,就等於是

        if (txtName.Text.Trim().Length == 0)
        {
            取 null;
        }
        else
        {
            取 txtName.Text.Trim();
        }

第三種的意思
??(雙問號),意思是取所賦值??左邊的,如果左邊為null,取所賦值??右邊的,所以上面例子等於是

   if (a==null)
        {
           取 0;        
        }
        else
        {
           取 a;
        }

2016年3月12日 星期六

string和byte[]的轉換 (C#)

string類型轉成byte[]:
byte[] byteArray = System.Text.Encoding.Default.GetBytes ( str );
反過來,byte[]轉成string:
string str = System.Text.Encoding.Default.GetString ( byteArray );

其它編碼方式的,如System.Text.UTF8Encoding,System.Text.UnicodeEncoding class等;例如:
string類型轉成ASCII byte[]:("01" 轉成 byte[] = new byte[]{ 0x30, 0x31})
byte[] byteArray = System.Text.Encoding.ASCII.GetBytes ( str );
ASCII byte[] 轉成string:(byte[] = new byte[]{ 0x30, 0x31} 轉成 "01")
string str = System.Text.Encoding.ASCII.GetString ( byteArray );

有時候還有這樣一些需求:
byte[] 轉成原16進制格式的string,例如0xae00cf, 轉換成 "ae00cf";new byte[]{ 0x30, 0x31}轉成"3031":
        public static string ToHexString ( byte[] bytes ) // 0xae00cf => "AE00CF "
        {
            
string hexString = string.Empty;
            
if ( bytes != null )
            
{
                StringBuilder strB 
= new StringBuilder ();

                
for ( int i = 0; i < bytes.Length; i++ )
                
{
                    strB.Append ( bytes[i].ToString ( 
"X2" ) );
                }

                hexString 
= strB.ToString ();
            }

            
return hexString;
        }

反過來,16進制格式的string 轉成byte[],例如, "ae00cf"轉換成0xae00cf,長度縮減一半;"3031"轉成new byte[]{ 0x30, 0x31}:
        public static byte[] GetBytes(string hexString, out int discarded)
        
{
            discarded 
= 0;
            
string newString = "";
            
char c;
            
// remove all none A-F, 0-9, characters
            for (int i=0; i<hexString.Length; i++)
            
{
                c 
= hexString[i];
                
if (IsHexDigit(c))
                    newString 
+= c;
                
else
                    discarded
++;
            }

            
// if odd number of characters, discard last character
            if (newString.Length % 2 != 0)
            
{
                discarded
++;
                newString 
= newString.Substring(0, newString.Length-1);
            }


            
int byteLength = newString.Length / 2;
            
byte[] bytes = new byte[byteLength];
            
string hex;
            
int j = 0;
            
for (int i=0; i<bytes.Length; i++)
            
{
                hex 
= new String(new Char[] {newString[j], newString[j+1]});
                bytes[i] 
= HexToByte(hex);
                j 
= j+2;
            }

            
return bytes;
        }