2019-07-21 17:06

C# 在 Enum 上增加附加資訊

C# 的 Enum 是個很方便的類型,如果可以再增加額外的資訊就更方便了,這裡利用 Attribute 去定義 Enum 額外的資訊,再用擴充方法取得 Enum 所屬的資訊。

用 Attribute 來定義有個好處,未來在增減 Enum 時可以一起進行修改,不用擔心會有遺漏而沒修改的問題。

void Main()
{
    PortAreaCode.F1Front.GetFloor().Dump(); /* F1 */
}


public enum PortAreaCode
{
    [AreaMeta("None", 0)]
    None,

    [AreaMeta("F1", 1)]
    F1Front,

    [AreaMeta("F2", 1)]
    F2Front,
}



/// <summary>PortAreaCode 額外附屬資訊定義的 Attribute</summary>
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
class AreaMetaAttribute : Attribute
{
    public string Floor { get; private set; }
    public int WarehouseId { get; private set; }

    public AreaMetaAttribute() : this("None", 0) { }

    public AreaMetaAttribute(string floor, int warehouseId)
    {
        Floor = floor;
        WarehouseId = warehouseId;
    }
}



/// <summary>PortAreaCode 的擴充方法</summary>
public static class PortAreaCodeExtensions
{
    private static AreaMetaAttribute _defaultMeta = new AreaMetaAttribute();

    private static AreaMetaAttribute getMeta(PortAreaCode value)
    {
        FieldInfo field = typeof(PortAreaCode).GetField(value.ToString());
        if(field == null) { return _defaultMeta; }

        var meta = field.GetCustomAttribute<AreaMetaAttribute>();
        return meta ?? _defaultMeta;
    }

    public static string GetFloor(this PortAreaCode value)
    {
        return getMeta(value).Floor;
    }

    public static int GetWarehouseId(this PortAreaCode value)
    {
        return getMeta(value).WarehouseId;
    }
}
2019-07-21 16:29

C# 用 gzip 壓縮字串並取得 base64 字串

這個使用方式的效果是有但書的,當 Source 的重複率不高壓縮的效果就不會好,再加上 base64 就是用可見文字去表示 byte 值,這會讓 base64 後的結果比 byte array 還要長,所以壓縮率沒有到達一定的程度下,輸出反而會比 Source 的字串還要長。

//using System.IO.Compression;


void Main()
{
    string text = "OptionPostal,OptionClassType,OptionTalentItem";
    text.Length.Dump(); /* 45 */

    string compressBase64 = compress(text);
    compressBase64.Length.Dump(); /* 76 */
    compressBase64.Dump(); 
    /* H4sIAAAAAAAEAPMvKMnMzwvILy5JzNHxB3OccxKLi0MqC1Kh/JDEnNS8Es+S1FwAaY6qVC0AAAA= */

    string decompressText = decompress(compressBase64);
    decompressText.Dump(); 
    /* OptionPostal,OptionClassType,OptionTalentItem */

    text = "OptionPostal,OptionClassType,OptionTalentItem,OptionPostal,OptionClassType,OptionTalentItem,OptionPostal,OptionClassType,OptionTalentItem,OptionPostal,OptionClassType,OptionTalentItem";
    text.Length.Dump(); /* 183 */

    compressBase64 = compress(text);
    compressBase64.Length.Dump(); /* 84 */
    compressBase64.Dump(); 
    /* H4sIAAAAAAAEAPMvKMnMzwvILy5JzNHxB3OccxKLi0MqC1Kh/JDEnNS8Es+S1FyowCBQDQBPmlWktwAAAA== */

}


/*壓縮*/
private static string compress(string text)
{
    if (string.IsNullOrEmpty(text)) { return text; }

    byte[] buffer = Encoding.UTF8.GetBytes(text);

    using (var outStream = new MemoryStream())
    using (var zip = new GZipStream(outStream, CompressionMode.Compress))
    {
        zip.Write(buffer, 0, buffer.Length);
        zip.Close();

        string compressedBase64 = Convert.ToBase64String(outStream.ToArray());
        return compressedBase64;
    }
}


/*解壓縮*/
private static string decompress(string compressed)
{
    if (string.IsNullOrEmpty(compressed)) { return compressed; }

    byte[] buffer = Convert.FromBase64String(compressed);

    using (var inStream = new MemoryStream(buffer))
    using (var outStream = new MemoryStream())
    using (var zip = new GZipStream(inStream, CompressionMode.Decompress))
    {
        zip.CopyTo(outStream);
        zip.Close();

        string text = Encoding.UTF8.GetString(outStream.ToArray());
        return text;
    }
}
2019-07-21 15:53

產生 IP v6 的 mask byte array

int length = 121; /* total 128 */

var mask = new byte[16];

for (int i = 0; i < 16; i++)
{
    mask[i] = 0xff;
    if (length > -8) { length -= 8; }
    if (length < 0) { mask[i] = (byte)(mask[i] << -length); }
    /* 當 length 出現負值時代表需要進行位移 */
}

BitConverter.ToString(mask).Dump();
/* FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-80 */
2019-07-21 15:43

C# DateTimeOffset Parse Patch

DateTimeOffset 在 Parse 時會使用 Local TimeZone,這會與期望的 TimeZone 產生偏差,需要進行差值修補。

//TimeZoneInfo.GetSystemTimeZones().Dump();

/* (UTC+02:00) 開羅 */
var zone = TimeZoneInfo.FindSystemTimeZoneById("Egypt Standard Time");
zone.Dump();

var date = DateTimeOffset.Parse("2019-07-01 15:00:00");
date.Dump(); /* 2019/7/1 下午 03:00:00 +08:00 */

var diff = date.Offset - zone.BaseUtcOffset;
diff.Dump(); /* 06:00:00 */

date = date.Add(diff);
date.Dump(); /* 2019/7/1 下午 09:00:00 +08:00 */

date = TimeZoneInfo.ConvertTime(date, zone);
date.Dump(); /* 2019/7/1 下午 03:00:00 +02:00 */
2019-07-19 16:46

WCF IP Filter

<!-- Web.config -->
<system.serviceModel>
  <extensions>
    <behaviorExtensions>
      <add name="ipFilter" type="XXX.XXX.IpFilterElement, XXX.XXX" />
    </behaviorExtensions>
  </extensions>

  <!-- .... -->

  <behaviors>
    <serviceBehaviors>
      <behavior>
        <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
        <serviceDebug includeExceptionDetailInFaults="true" />
        <ipFilter allow="192.168.1.0/24, 127.0.0.1" />
      </behavior>
    </serviceBehaviors>
  </behaviors>
</system.serviceModel>

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization;
using System.Security.Authentication;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using JustWin.API.Extensions;


public class IpFilterElement : BehaviorExtensionElement
{
    [ConfigurationProperty("allow", IsRequired = true)]
    public virtual string Allow
    {
        get { return this["allow"] as string; }
        set { this["allow"] = value; }
    }

    public override Type BehaviorType
    {
        get { return typeof(IpFilterBehaviour); }
    }

    protected override object CreateBehavior()
    {
        return new IpFilterBehaviour(Allow);
    }
}




public class IpFilterBehaviour : IDispatchMessageInspector, IServiceBehavior
{
    private readonly List<IPAddressRange> _allowList;

    public IpFilterBehaviour(string allow)
    {
        _allowList = allow.Split(',').Select(x => new IPAddressRange(x)).ToList();
    }


    void IServiceBehavior.Validate(ServiceDescription service, ServiceHostBase host)
    {
    }

    void IServiceBehavior.AddBindingParameters(ServiceDescription service, ServiceHostBase host, Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters)
    {
    }

    void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription service, ServiceHostBase host)
    {
        foreach (ChannelDispatcher dispatcher in host.ChannelDispatchers)
        foreach (EndpointDispatcher endpoint in dispatcher.Endpoints)
        {
            endpoint.DispatchRuntime.MessageInspectors.Add(this);
        }
    }



    object IDispatchMessageInspector.AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
    {
        var remoteEndpoint = request.Properties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;

        var address = IPAddress.Parse(remoteEndpoint.Address);
        if(_allowList.Any(x => x.IsMatch(address))) { return null; }

        request = null;
        return new AuthenticationException($"IP address ({remoteEndpoint.Address}) is not allowed.");
    }


    void IDispatchMessageInspector.BeforeSendReply(ref Message reply, object correlationState)
    {
        var ex = correlationState as Exception;
        if (ex == null) { return; }

        MessageFault messageFault = MessageFault.CreateFault(
            new FaultCode("Sender"),
            new FaultReason(ex.Message),
            ex,
            new NetDataContractSerializer()
        );

        reply = Message.CreateMessage(reply.Version, messageFault, null);
    }

}







public class IPAddressRange
{
    private readonly byte[] _rangeAddress;
    private readonly byte[] _rangeMask;

    public IPAddressRange(string ipAndMask)
    {
        string[] split = (ipAndMask + "/128").Split('/');

        var ip = IPAddress.Parse(split[0].Trim());

        int maskLength = int.Parse(split[1].Trim());
        if (ip.AddressFamily == AddressFamily.InterNetwork) { maskLength += 96; }

        _rangeMask = createMask(maskLength);

        _rangeAddress = ip.MapToIPv6().GetAddressBytes()
            .Select((x, i) => x & _rangeMask[i])
            .Select(x => (byte)x)
            .ToArray();
    }


    public bool IsMatch(IPAddress ip)
    {
        byte[] address = ip.MapToIPv6().GetAddressBytes();

        for (int i = 0; i < 16; i++)
        {
            if ((address[i] & _rangeMask[i]) != _rangeAddress[i]) { return false; }
        }

        return true;
    }



    private byte[] createMask(int length)
    {
        var mask = new byte[16];

        for (int i = 0; i < 16; i++)
        {
            mask[i] = 0xff;
            if (length > -8) { length -= 8; }
            if (length < 0) { mask[i] = (byte)(mask[i] << -length); }
        }
        return mask;
    }
}
2018-05-13 00:37

C# 從 Delegate 到 Lambda

/* 定義一個 delegate */
public delegate string MyDel(int id);


/* 宣告一個 Method */
public static string MethodA(int id)
{
    return " { " + id + " } ";
}


/* 將 MethodA 指派給一個 MyDel */
MyDel fu = MethodA;

/* 接著就可以這樣呼叫 delegate */
string result = fu(22);
System.Console.WriteLine(result);



/* 用 delegate 關鍵字建立 MyDel */
MyDel fu = delegate(int id)
{
    return " { " + id + " } ";
};



/* 用 Lambda 語法建立 MyDel */
MyDel fu = (int id) => {
    return " { " + id + " } ";
};



/* 因為 MyDel 已經定義參數為 int 了,所以可以省略 int */
MyDel fu = (id) => {
    return " { " + id + " } ";
};



/* 因為是單參數,所以可以省略小刮號 */
MyDel fu = id => {
    return " { " + id + " } ";
};



/* 接著是單純的 return,所以可以省略大刮號跟 return */
MyDel fu = id => " { " + id + " } ";

 


Ref:
匿名函式 (C# 程式設計手冊)
使用委派 (C# 程式設計手冊)
2018-05-12 23:58

Windows Server 磁碟清理


磁碟清理
DISM.exe /online /Cleanup-Image /StartComponentCleanup

清理系統 Service Pack 備份檔案
DISM.exe /Online /Cleanup-Image /SPSuperseded
2018-05-12 23:56

JavaScript 正規表示式 跳脫

RegExp.escape = function(str) {
    return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
};

/* use sample */
new RegExp(RegExp.escape("[te()st]"));