2020年12月11日 星期五

陣列操作

取得陣列運算結果
[{value:2,text:'male'},{value:7,text:'female'},]
arr.reduce((partialSum, a) => partialSum + a.value, 0) // return 9

去除重複元素
arr.filter((value, index, array) => array.indexOf(value) === index);

去除重複元素 by 屬性值
export function arrayDistinctByKey(arr: Array<any>, key: string): Array<any> {
    return arr.filter((value, index, array) => arr.filter(a=>a[key]==value[key])[0] === value);
}

移除(置換)單一元素
keyname: 用來比對的屬性,若未設定則用整個物件比對
newvalue: 欲置換的元素
function arrayRemove(arr, value, keyname,newvalue) { 
    if (keyname) {
        for (let a of arr) {
            if (a[keyname] == value[keyname]) {
                value = a;
                break;
            }
        }
    }
    let index = arr.indexOf(value);
    if (index > -1) {
        if (!newvalue) arr.splice(index, 1);
        else arr.splice(index, 1, newvalue);
    }
    return arr;
}

將一個陣列插入另一個陣列某個位置
arr1.splice(i,0,...arr2) // ...arr2 = arr2[0],arr2[1],...

結合
Array.prototype.push.apply(arr1, arr2); // 將arr2 的元素放入arr1
arrs=arrs1.concat(arrs2);

走訪
arrs.forEach(a=>a.項次=項次--);
arrs.forEach(a=>{
...                       
});    

過濾後選擇某些屬性
arr.filter(res=>res.checked).map(ele=>({value:ele.value,text:ele.name}));

2020年11月13日 星期五

不同使用者共用物件並同步雙向更新

建立共用類別
public class Filter
    {
        private string _filter;
        public string filter
        {
            get
            {
                return _filter;
            }
            set
            {
                _filter = value;
                NotifyDataChanged();
            }
        }
        public event Action OnChange;
        private void NotifyDataChanged() => OnChange?.Invoke();
    }

startup.cs 註冊為服務
public void ConfigureServices(IServiceCollection services)
        {           
            services.AddSingleton<Filter>();
        }

.razor 
====
注入服務
@inject demo1.Filter Filter

顯示於畫面
<input type="text" @bind="Filter.filter" @bind:event="oninput">

設定物件內容改變時通知以便刷新頁面
protected override void OnInitialized()
    {
        Filter.OnChange+= OnMyChangeHandler;
    }
 public void Dispose()
    {
        Filter.OnChange -= OnMyChangeHandler;
    }
    private async void OnMyChangeHandler()
    {
        await InvokeAsync(StateHasChanged);
    }

透過 Entity Framework Core 工具產生資料庫表格相關類別

開啟套件管理器主控台

安裝工具 (亦可透過 nuget)
Install-Package Microsoft.EntityFrameworkCore.Tools
Install-Package Microsoft.EntityFrameworkCore.SqlServer

產生檔案
Scaffold-DbContext Name=xxx Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models\xxx -Tables xxx,xxx -force

appsettings.json 建立連線字串
 "ConnectionStrings": {
    "xxx": "Server=xxx; Database=xxx; User Id=xxx; Password=xxx;"  
  }

Blazor 學習心得

實作篩選方塊

html
<input type="text" @bind="filter"   @bind:event="oninput"> <= 若要隨時輸入資料立刻篩選則改變預設事件onchange 為 oninput,否則失去焦點才會改變 filter 
@foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }

c#
private IEnumerable<WeatherForecast> forecasts => forecastss.Where(a => filter!=default && a.Summary.Contains( filter) || filter == default || filter == "");

呼叫 javascript 函數

html
@inject IJSRuntime JS;
<script>
        window.Alert = function (message) {
            alert(message);
        }
</script>

c#
await JS.InvokeVoidAsync("Alert", "123");

存取資料庫

startup.cs
public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContextFactory<xxxContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("xxx")));

xxxContext.cs
刪除空參數建構者避免出錯

設定元件
@implements IDisposable
@inject Microsoft.EntityFrameworkCore.IDbContextFactory<xxxContext> DbFactory

產生DbContext
xxxContext db;
protected override async Task OnInitializedAsync()
    {
        db = DbFactory.CreateDbContext();
        await base.OnInitializedAsync();
    }

釋放DbContext
public void Dispose()
    {
        db.Dispose();
    }


2020年11月5日 星期四

透過 Entity Framework Core 命令列介面產生資料庫表格相關類別 (for Visual Studio Code)

安裝工具
dotnet tool install --global dotnet-ef

更新工具
dotnet tool update --global dotnet-ef

安裝套件
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.SqlServer

建立工作
{
            "label": "更新資料庫",
            "type": "process",
            "command": "dotnet",
            "args": [
                "ef",
                "-p",
                "${workspaceFolder}\\xxx.csproj",
                "dbcontext",
                "scaffold",
                "Name=[appsettings.json資料庫連線名稱]",
                "Microsoft.EntityFrameworkCore.SqlServer",
                "-o",
                "${workspaceFolder}\\xxx\\Models\\[用資料庫名稱區隔不同的資料夾避免名稱衝突]",
                "-t",
                "xxx",                 
                "-f"
            ],
            "problemMatcher": "$msCompile"
}

Visual Studio Code 發佈方式

透過工作命令

加入工作
{
                "label": "發行",
                "type": "process",
                "command": "發佈.bat",
                "args": [],
}

發佈.bat
dotnet build xxx\xxx.csproj -c Release -o xxx
copy app_offline.htm xxx
dotnet publish xxx\xxx.csproj -c Release -o xxx --no-build
del xxx\app_offline.htm
※app_offline.htm 會造成app pool 停止,但這樣複製檔案才能避免鎖定問題

執行工作 : 終端機 > 執行工作

2020年11月4日 星期三

ASP.NET Core MVC 資料庫連線設定與使用方式

資料庫連線設定與使用方式
appsettings.json 設定連線字串
"ConnectionStrings": {
"xxx": "Server=xxx; Database=xxx; User Id=xxx; Password=xxx;"
}
startup.cs 加入連線
public void ConfigureServices (IServiceCollection services) {
services.AddDbContext<xxxContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("xxx")));
xxxController.cs 使用連線
public class xxxController : ControllerBase {
private readonly xxxContext db;
public xxxController(xxxContext db)
{
this.db = db;
}

若要單獨使用不透過controller,則須擴展xxxContext.cs
public partial class xxxContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
                IConfigurationRoot configuration = new ConfigurationBuilder()
                .SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
                .AddJsonFile("appsettings.json")
                .Build();
                optionsBuilder.UseSqlServer(configuration.GetConnectionString("xxx"));
            }
        }
使用:
using (var db=new xxxContext()) {
...
}

※存取關聯table 要先include,否則產生集合(.ToList())後關聯物件都會是null
db.aaa.Where(a =>...).Include(a=>a.xxx) // 事後才能取得 a.xxx.ooo
※ 使用 .GroupBy 前必須先 .AsEnumerable 否則可能會報錯
※ 使用內建連線,action 回傳型態不能為 void,否則會造成 SaveChangesAsync() 出錯,因為會提前dispose(不會等待執行)

.net 7 避免 sql server table 有trigger 造成 table 無法更新 (有trigger 會改用舊架構,非必要不要使用)

擴展xxxContext.cs
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
        {
            configurationBuilder.Conventions.Add(_ => new BlankTriggerAddingConvention());
        }
public class BlankTriggerAddingConvention : IModelFinalizingConvention
    {
        public virtual void ProcessModelFinalizing(
            IConventionModelBuilder modelBuilder,
            IConventionContext<IConventionModelBuilder> context)
        {
            foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
            {
                var table = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table);
                if (table != null
                    && entityType.GetDeclaredTriggers().All(t => t.GetDatabaseName(table.Value) == null))
                {
                    entityType.Builder.HasTrigger(table.Value.Name + "_Trigger");
                }

                foreach (var fragment in entityType.GetMappingFragments(StoreObjectType.Table))
                {
                    if (entityType.GetDeclaredTriggers().All(t => t.GetDatabaseName(fragment.StoreObject) == null))
                    {
                        entityType.Builder.HasTrigger(fragment.StoreObject.Name + "_Trigger");
                    }
                }
            }
        }
    }

自動延遲載入

安裝套件 Microsoft.EntityFrameworkCore.Proxies
startup.cs 
services.AddDbContext<xxx>(options =>
 options.UseSqlServer(Configuration.GetConnectionString("圓桌基本資料")).UseLazyLoadingProxies());       
擴展xxxContext.cs
optionsBuilder.UseSqlServer(connString).UseLazyLoadingProxies();
※ 於 .net 6 似乎會造成 EF 無法使用,待確認

2020年11月3日 星期二

Angular 設定容器高度為可用剩餘高度

使用 attribute directive

建立 class : ng generate directive bodyinner --module app
constructor(private el: ElementRef) {
}
ngAfterViewInit() {
    this.el.nativeElement.style.height = (window.innerHeight - this.el.nativeElement.offsetTop) + 'px';
}

html:
<div appBodyinner style="overflow-y:auto"></div>


2020年10月27日 星期二

bootstrap 圖案美化

立體按鈕
.glyphicon-plus {
        box-shadow: 0 1px 2px 0 rgba(60,64,67,0.302), 0 1px 3px 1px rgba(60,64,67,0.149);
        border-radius:28px;
        padding:5px 4px 5px 5px;background-color:white;

2020年10月8日 星期四

讓div 可以拖拉或改變大小

拖拉
$(".詢問視窗").draggable();

改變大小
$(".詢問視窗").resizable({handles: "n,  w"}); // n 表示上方邊緣可以拖拉

2020年10月6日 星期二

上傳檔案存入資料庫後取得並轉為 memorystream

var model = db.file.Find(id);
var filecontent=System.Text.Encoding.ASCII.GetString(model.content).Split(',')[1]; // content欄位為varbinary(max)
using (var stream = new System.IO.MemoryStream(Convert.FromBase64String(filecontent)))
{

2020年9月23日 星期三

多工作業(平行處理)注意事項

※使用epplus,不要在task 中取得sheet,必須先取得後放入變數,在task 中存取該變數(pck.Workbook.Worksheets[i] 非安全執行緒)
List<ExcelWorksheet> sheets = new List<ExcelWorksheet>();
using (ExcelPackage pck = new ExcelPackage(new System.IO.FileInfo(excel_template), true)) {
        pck.Workbook.Worksheets.ForEach(a => sheets.Add(a));
        Parallel.For(1, 7,
               i => {
                   var sheet = sheets.ElementAt(i-1);
※ 在多工中 epplus 使用 insertrow 會造成不定時出現錯誤訊息 xxx updatecrossreference xxx

※多個task 不要放在 List 中,要改用 ConcurrentBag 存放(代表安全執行緒的未排序物件集合),避免出現錯誤訊息:...集合已修改...
ConcurrentBag<Task> tasks = new ConcurrentBag<Task>();
tasks.Add(Task.Run(() => {...}));
tasks.Add(Task.Run(() => {...}));
Task.WaitAll(tasks.ToArray());

※entity framework 非安全執行緒,關聯資料無法在其他執行緒中取得,多工作業中需另外重新取得資料,且同一個db無法跨執行緒共用,會引發未預期錯誤
※多工作業中若會連線資料庫,要限制同時執行的工作數量,避免造成資料庫可用連線耗盡
using (var db = new ERPEntities())
{
var 訂單ids=db.訂單.Where(a=>a.日期==今天).Select(a=>a.id).ToList();
Parallel.ForEach(訂單ids,new ParallelOptions { MaxDegreeOfParallelism = 8 }, (訂單id) =>
{
    using (var db = new ERPEntities())
    {
        var 訂單 = db.訂單.Single(a => a.id == 訂單id);

※DataTable 非安全執行緒,可以在多工作業查詢資料但不能異動資料

Parallel.ForEach 非同步版用法 (.Net 6 以上)
 await Parallel.ForEachAsync(products, new ParallelOptions { MaxDegreeOfParallelism = 5 }, async (product, token) =>
{
            ....
});

2020年9月21日 星期一

使用 epplus 加入圖表

以折線圖為例
var chart = sheet.Drawings.AddChart("圖表1", eChartType.Line);
var series= (ExcelLineChartSerie)chart.Series.Add(sheet.Cells[2,2,100,2], sheet.Cells[2,1,100,1]);
series.HeaderAddress = sheet.Cells[1, 2];
            

2020年8月11日 星期二

2020年8月7日 星期五

讓 IEnumerable 能夠使用 indexof 得知物件順位

public static int IndexOf<T>(this IEnumerable<T> list, T obj) where T : class

{

    return list.TakeWhile(x => x != obj).Count();

}


var list=db.xxx.Where(...).AsEnumerable();

foreach (var a in list)

  list.IndexOf(a) <= 物件順位

2020年7月31日 星期五

使用 $timeout 注意

$scope destroy 時要 cancel,因為不會自動取消呼叫,若固定周期呼叫則會持續呼叫
$scope.timer = $timeout($scope.onTimeout, 10000);
 $scope.$on(
        "$destroy",
        function (event) {
            $timeout.cancel($scope.timer);
        }
    );

2020年6月29日 星期一

trigger 使用注意是否為其他trigger 引發

若只考慮是 update table 引發的情況則加入以下判斷
IF TRIGGER_NESTLEVEL() <= 1
這樣若在其他trigger 中觸發,就不會執行
例如 insert trigger 中會 update table,則會同時觸發 update trigger
此時須考慮 update trigger 中要執行的事情是否須避開因為 insert trigger 而引發的情況

2020年6月17日 星期三

NTPC OpenData API

api 列表

使用方式 (以政府行政機關辦公日曆表為例)
====
string url = "https://data.ntpc.gov.tw/api/datasets/308DCD75-6434-45BC-A95F-584DA4FED251/json?page=0&size=10000";
var request = WebRequest.Create(url);
request.Method = "GET";
request.ContentType = "application/json;charset=UTF-8";
var response = request.GetResponse() as HttpWebResponse;
var responseStream = response.GetResponseStream();
var reader = new StreamReader(responseStream, System.Text.Encoding.GetEncoding("utf-8"));
var jsonlist = reader.ReadToEnd();
var list = JsonConvert.DeserializeObject>(jsonlist);
var 今年資料s = list.Where(a => a.Value("date").Year == DateTime.Now.Year).ToList();

2020年5月28日 星期四

將字串從某個關鍵字串截斷,只保留關鍵字串前面的部分

 public static string 截斷(this string text, string 關鍵字)
        {
            if (text != default && text.Contains(關鍵字)) return System.Text.RegularExpressions.Regex.Split(text, 關鍵字)[0];
            else return text;
        }

2020年5月22日 星期五

用程式讀取 exchange server 信箱

1. 安裝套件: Microsoft.Exchange.WebServices
2. 讀取信箱
var es = new Microsoft.Exchange.WebServices.Data.ExchangeService(ExchangeVersion.Exchange2010_SP1);
es.Credentials = new Microsoft.Exchange.WebServices.Data.WebCredentials(帳號, 密碼, 網域);
es.Url = new Uri("https://.../ews/Exchange.asmx"); // Server路徑
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; }; // 確保登入信箱不會失敗
SearchFilter.SearchFilterCollection searchFilterCollection = new SearchFilter.SearchFilterCollection(LogicalOperator.And); // 設定條件類型為全部還是任一
                                            searchFilterCollection.Add(new SearchFilter.IsGreaterThan(EmailMessageSchema.DateTimeReceived, 上次資料時間)); // 加入條件
PropertySet itempropertyset = new PropertySet(BasePropertySet.FirstClassProperties); // 為了讀取內文
itempropertyset.RequestedBodyType = BodyType.Text;
ItemView itemview = new ItemView(1000);
itemview.PropertySet = itempropertyset;
var elements = es.FindItems(WellKnownFolderName.Inbox, searchFilterCollection, itemview).Where(a => a is EmailMessage).ToList(); // 尋找收件匣信件 (WellKnownFolderName.SentItems : 寄件備份)
foreach (EmailMessage email in elements ) // 轉型為 EmailMessage
{
email.Load(itempropertyset); // 為了讀取內文
email.From.Address : 寄件者
email.ToRecipients、email.DisplayTo : 收件者
email.Body.Text : 內文
email.InternetMessageId : 透過此欄位可找出回信
email.InReplyTo : 若為寄件備份且是回信,等於來源信件 InternetMessageId
email.Id.UniqueId : id
                 
依據id 找出單一信件:
EmailMessage.Bind(es, new ItemId(id));                      

取得附件檔案
public static IEnumerable<(string 檔名, byte[] 檔案內容)> 取得附加檔案(this Microsoft.Exchange.WebServices.Data.EmailMessage email)
        {
            foreach (var attachment in email.Attachments.OrderByDescending(a => a.Size))
            {
                if (attachment is Microsoft.Exchange.WebServices.Data.FileAttachment && !attachment.IsInline)
                {
                    Microsoft.Exchange.WebServices.Data.FileAttachment 附件檔案 = attachment as Microsoft.Exchange.WebServices.Data.FileAttachment;
                    using (var mem = new System.IO.MemoryStream())
                    {
                        附件檔案.Load(mem);
                        yield return (附件檔案.Name, mem.ToArray());
                    }
                }
            }
            yield return default;
        }


若要存取 Exchange Online 雲端信箱 ,認證方式較為複雜
1. 參考此網址,於 server 註冊應用程式提供授權
2. 設定證書及信箱語法如下
var cca = Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
.Create(應用程式識別碼)
.WithClientSecret(用戶端密碼) // 最多可設兩年後到期
.WithTenantId(目錄識別碼)
.Build();
// The permission scope required for EWS access
var ewsScopes = new string[] { "https://outlook.office365.com/.default" };
//Make the token request
var authResult = await cca.AcquireTokenForClient(ewsScopes).ExecuteAsync();
var ews = new ExchangeService();
ews.Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx");
ews.Credentials = new OAuthCredentials(authResult.AccessToken);
//Impersonate the mailbox you'd like to access.
ews.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, "xxx@www.xxx");

使用 ews 寄信 (exchange online 不支援 smtpclient)

var mms = new EmailMessage(ews);
mms.ToRecipients.Add(new EmailAddress("xxx@www.xxx"));
mms.CcRecipients.Add(new EmailAddress("xxx@www.xxx"));
mms.BccRecipients.Add(new EmailAddress("xxx@www.xxx"));
mms.Subject = "xxx";    
mms.Body = new MessageBody(BodyType.HTML, "xxx");
mms.Attachments.AddFileAttachment("xxx.xxx",stream);
mms.Send(); // 不會留信件在寄件備份

2020年5月11日 星期一

設定div 填滿可用空間,內容不足則出現捲軸

.directive('divwh', function ($timeout) {
        return {
            link: function (scope, elem, attrs) {
                var positioner = function () {
                    var h = $(window).height() - $(elem).offset().top - 5; // 使用最多高度 ,5px 緩衝(考慮捲軸空間)     
                    var w = $(window).width() - 190 - 10;   // 使用最多寬度,左邊預留190px,10px 緩衝(考慮捲軸空間)
                    $(elem).height(h);
                    $(elem).width(w);
                }
                $(window).resize(function () {
                    positioner();
                });
                $timeout(function () {
                    positioner();
                });
            }
        };
    });

2020年5月7日 星期四

table凍結欄跟列

假設凍結第一列及前三欄

css
====
 div.divunfinish {
  overflow: auto;height:300px; /* 非必要 */
}
     div.divunfinish   thead td {
  position: -webkit-sticky; /* for Safari */
  position: sticky;
  top: 0;background-color:Brown;
}
div.divunfinish thead td:nth-child(1) {
  left: 0;
  z-index: 1;
}
div.divunfinish thead td:nth-child(2) {
  left: 40px;
  z-index: 1;
}
div.divunfinish thead td:nth-child(3) {
  left: 100px;
  z-index: 1;
}
div.divunfinish tbody td:nth-child(1) {
  position: -webkit-sticky; /* for Safari */
  position: sticky;
  left: 0;background-color:white;
}
div.divunfinish tbody td:nth-child(2) {
  position: -webkit-sticky; /* for Safari */
  position: sticky;
  left: 40px;background-color:white;
}
div.divunfinish tbody td:nth-child(3) {
  position: -webkit-sticky; /* for Safari */
  position: sticky;
  left: 100px;background-color:white;
}

html
====
<div class="divunfinish">
<table style="width: 1530px;" >
<thead>
<tr>
...
<tbody>
<tr>
...

※ bootstrapvue b-table 若有設定為 responsive 則會造成凍結失效

2020年2月13日 星期四

使用 SignalR 讓 server 主動推播資料給 client

1. 安裝套件: Microsoft.AspNet.SignalR、Microsoft.AspNet.Cors、Microsoft.Owin.Cors

2. Startup.cs 加入
public void Configuration(IAppBuilder app)
{
// 不考慮跨網域連結
        app.MapSignalR();
// 考慮跨網域連結(僅針對MVC,傳統aspx 無效)
var corsPolicy = new CorsPolicy
{
AllowAnyMethod = true,
AllowAnyHeader = true,
SupportsCredentials = true,
};
corsPolicy.Origins.Add("https://somedomain"); // 設定來源網站白名單
var corsOptions = new CorsOptions
{
PolicyProvider = new CorsPolicyProvider
{
PolicyResolver = context =>
{
context.Headers.Add("Access-Control-Allow-Credentials", new[] { "true" });
return Task.FromResult(corsPolicy);
}
}
};
app.Map("/signalr", map =>
{
// Setup the CORS middleware to run before SignalR.
// By default this will allow all origins. You can 
// configure the set of origins and/or http verbs by
// providing a cors options with a different policy.
map.UseCors(corsOptions);                
var hubConfiguration = new HubConfiguration
{
// You can enable JSONP by uncommenting line below.
// JSONP requests are insecure but some older browsers (and some
// versions of IE) require JSONP to work cross domain
// EnableJSONP = true
};
// Run the SignalR pipeline. We're not using MapSignalR
// since this branch already runs under the "/signalr"
// path.
map.RunSignalR(hubConfiguration);
});
}

3. 新增class
public class GetHub : Microsoft.AspNet.SignalR.Hub
    {
private static IHubContext context = GlobalHost.ConnectionManager.GetHubContext<GetHub>();

class ConnectionMapping<T>
{
private readonly Dictionary<T, HashSet<string>> _connections =
new Dictionary<T, HashSet<string>>();

public int Count
{
get
{
return _connections.Count;
}
}

public void Add(T key, string connectionId)
{
lock (_connections)
{
HashSet<string> connections;
if (!_connections.TryGetValue(key, out connections))
{
connections = new HashSet<string>();
_connections.Add(key, connections);
}

lock (connections)
{
connections.Add(connectionId);
}
}
}

public IEnumerable<string> GetConnections(T key)
{
HashSet<string> connections;
if (_connections.TryGetValue(key, out connections))
{
return connections;
}

return Enumerable.Empty<string>();
}

public void Remove(T key, string connectionId)
{
lock (_connections)
{
HashSet<string> connections;
if (!_connections.TryGetValue(key, out connections))
{
return;
}

lock (connections)
{
connections.Remove(connectionId);

if (connections.Count == 0)
{
_connections.Remove(key);
}
}
}
}
}

private readonly static ConnectionMapping<string> _connections =
   new ConnectionMapping<string>();

public static void SendChatMessage(string who,string name, string message)
{  
foreach (var connectionId in _connections.GetConnections(who))
{
context.Clients.Client(connectionId).Invoke(name, message);
}
}

public override Task OnConnected()
{
string name = Context.User.Identity.Name;

_connections.Add(name, Context.ConnectionId);

return base.OnConnected();
}

public override Task OnDisconnected(bool stopCalled)
{
string name = Context.User.Identity.Name;

_connections.Remove(name, Context.ConnectionId);

return base.OnDisconnected(stopCalled);
}

public override Task OnReconnected()
{
string name = Context.User.Identity.Name;

if (!_connections.GetConnections(name).Contains(Context.ConnectionId))
{
_connections.Add(name, Context.ConnectionId);
}

return base.OnReconnected();
}
    }

4. .cs 針對要推播資料的地方加入以下程式
//推撥給所有人
var hub = Microsoft.AspNet.SignalR.GlobalHost.ConnectionManager.GetHubContext<GetHub>();
hub.Clients.All.發送訊息(msg); 
//推撥給同一網頁前端
string username = HttpContext.Current.User.Identity.Name;
GetHub.SendChatMessage(username, name, message); 
//推播給特定連線id
hub.Clients.Client(connid).Invoke(hubmethod, 回報訊息); 

5. .cshtml 引入 js
<script src="~/Scripts/jquery.signalR-2.4.1.min.js"></script>

6. .js 接收 server 推播
var connection = $.hubConnection('<%: ResolveUrl("~/signalr") %>'); // 若不指定參數會預設為 /signalr,造成在正式環境找不到路徑(若有虛擬目錄) 
ps. asp.net mvc => @Url.Content("~/signalr")
var connid; // 呼叫後端時帶入,以便可以指定回報訊息給此連線
connection.disconnected(function () {
            setTimeout(function () {
                connection.start({ transport:'webSockets'}).done(function () {
                        connid = connection.id;
                    });
            }, 5000); // Restart connection after 5 seconds.
        });
var proxy = connection.createHubProxy('GetHub');
proxy.on('發送訊息', function (msg) {
alert(msg);
});
connection.start({ transport:'webSockets'}).done(function () {// transport:'webSockets'  強制指定傳輸方式為webSockets (否則就算可以使用webSockets但自動選擇時不見得會採用此方式)
                        connid = connection.id;
                    }); 

若為asp.net 網站,需修改 web.config 加入
<add key="owin:AutomaticAppStartup" value="true" />
<system.webServer>
<validation validateIntegratedModeConfiguration="false" /> 

若瀏覽器遇到此錯誤: WebSocket connection to 'ws://*****' failed: Error during WebSocket handshake: net::ERR_CONNECTION_RESET
請修改 web.config httpRuntime 加入 targetFramework="xxx" 
須為4.5以上,若未指定則預設為4.0,而4.0 不支援 WebSocket 傳輸方式,雖然實際會改用 Long Polling ,一樣能正常運作但效能較差

iis 7.5 以前不支援webSockets 方式傳輸(最佳傳輸方式),iis 8.0 以後需要新增伺服器角色"websockets通訊協定"才能支援,確認目前使用何種方式傳輸:
connection.start().done(function () {
    console.log("Connected, transport = " + connection.transport.name);
});

若iis 有設定 工作者處理序數上限>1 則要另外架構 backplane,以便訊息可以在不同 process 間傳遞,參考這裡
 

2020年1月31日 星期五

查詢AD 中的email

SELECT          userPrincipalName email,sAMAccountName username, displayName
                            FROM               OPENQUERY(ADSI,
                                                        'SELECT msExchHideFromAddressLists,userPrincipalName,displayName,sAMAccountName
FROM  ''LDAP://domain.com/OU=組織單位,DC=domain,DC=com''
WHERE objectClass =  ''User''
')
where msExchHideFromAddressLists is null or msExchHideFromAddressLists=0

※msExchHideFromAddressLists=1 表示信箱隱藏

AD 屬性列表

2020年1月14日 星期二

讓文字更清楚

ui-select 顏色不要變淡
.text-muted {
    color: unset;
}

form-control 顏色不要變淡
.form-control {
    color: unset;
}

避免以上設定造成 datepicker 非當月日期顏色沒有變淡而不容易判斷
.uib-daypicker .text-muted {
    color: #777;
}

form-control 禁止狀態顏色不要變淡且底色不要變灰
.form-control[disabled] {
    background-color: #fff !important;
    color: unset;
}

ui-select 禁止狀態顏色不要變淡
.ui-select-match .btn[disabled] {
    opacity: unset;
}

Entity Framework 建立新物件並儲存後馬上取得關聯資料

使用 CreateProxy 建立物件,不要直接 new var newmodel = _contextXXX.CreateProxy<yyy>(); ... _contextXXX.yyy.Add(newmodel); await _contextXXX.SaveC...