设备和程序绑定

Former-commit-id: 7b99ecd8268ec890e4016f4d2ab657fd7b010204
TangShanKaiPing
wanggang 6 years ago
parent c26a182578
commit 8a96097cbb

@ -0,0 +1,75 @@
using System.Collections.Generic;
using System.Text;
namespace Infrastructure.Extensions
{
public static class Base62Extensions
{
private static string characterSet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
public static string Base62Encode(this string value)
{
var arr = Encoding.UTF8.GetBytes(value);
return Base62Encode(arr);
}
public static string Base62Decode(this string value)
{
var arr = new byte[value.Length];
for (var i = 0; i < arr.Length; i++)
{
arr[i] = (byte)characterSet.IndexOf(value[i]);
}
return Base62Decode(arr);
}
public static string Base62Encode(this byte[] value)
{
var converted = BaseConvert(value, 256, 62);
var builder = new StringBuilder();
for (var i = 0; i < converted.Length; i++)
{
builder.Append(characterSet[converted[i]]);
}
return builder.ToString();
}
public static string Base62Decode(this byte[] value)
{
var converted = BaseConvert(value, 62, 256);
return Encoding.UTF8.GetString(converted, 0, converted.Length);
}
private static byte[] BaseConvert(byte[] source, int sourceBase, int targetBase)
{
var result = new List<int>();
int count = 0;
while ((count = source.Length) > 0)
{
var quotient = new List<byte>();
int remainder = 0;
for (var i = 0; i != count; i++)
{
int accumulator = source[i] + remainder * sourceBase;
byte digit = System.Convert.ToByte((accumulator - (accumulator % targetBase)) / targetBase);
remainder = accumulator % targetBase;
if (quotient.Count > 0 || digit != 0)
{
quotient.Add(digit);
}
}
result.Insert(0, remainder);
source = quotient.ToArray();
}
var output = new byte[result.Count];
for (int i = 0; i < result.Count; i++)
output[i] = (byte)result[i];
return output;
}
}
}

@ -6,7 +6,7 @@ namespace Infrastructure.Extensions
{ {
public static long ToUnixTimeMilliseconds(this DateTime dateTime) public static long ToUnixTimeMilliseconds(this DateTime dateTime)
{ {
return new DateTimeOffset().ToUnixTimeMilliseconds(); return new DateTimeOffset(dateTime).ToUnixTimeMilliseconds();
} }
public static DateTime FromUnixTimeMilliseconds(this string timestrap) public static DateTime FromUnixTimeMilliseconds(this string timestrap)

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -6,6 +7,8 @@ using System.Net;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Net.Sockets; using System.Net.Sockets;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace Infrastructure.Extensions namespace Infrastructure.Extensions

@ -18,7 +18,9 @@ namespace Infrastructure.Extensions
{ {
var hex = new StringBuilder(bytes.Length * 2); var hex = new StringBuilder(bytes.Length * 2);
foreach (byte b in bytes) foreach (byte b in bytes)
{
hex.AppendFormat("{0:x2}", b); hex.AppendFormat("{0:x2}", b);
}
return hex.ToString(); return hex.ToString();
} }
} }

@ -1,7 +1,8 @@
using HtmlAgilityPack;
using System; using System;
using System.Linq; using System.Linq;
using System.Security.Cryptography;
using System.Text; using System.Text;
using HtmlAgilityPack;
namespace Infrastructure.Extensions namespace Infrastructure.Extensions
{ {
@ -58,5 +59,53 @@ namespace Infrastructure.Extensions
{ {
return Enumerable.Range(0, hex.Length / 2).Select(x => Convert.ToByte(hex.Substring(x * 2, 2), 16)).ToArray(); return Enumerable.Range(0, hex.Length / 2).Select(x => Convert.ToByte(hex.Substring(x * 2, 2), 16)).ToArray();
} }
public static string DESEncrypt(this string value, string key)
{
using (var des = new DESCryptoServiceProvider())
{
var inputByteArray = Encoding.Default.GetBytes(value);
var md5Key = key.Md5().Substring(0, 8);
des.Key = ASCIIEncoding.ASCII.GetBytes(md5Key);
des.IV = ASCIIEncoding.ASCII.GetBytes(md5Key);
using (var ms = new System.IO.MemoryStream())
{
using (var cs = new CryptoStream(ms, des.CreateEncryptor(), CryptoStreamMode.Write))
{
cs.Write(inputByteArray, 0, inputByteArray.Length);
cs.FlushFinalBlock();
var ret = new StringBuilder();
foreach (var item in ms.ToArray())
{
ret.AppendFormat("{0:X2}", item);
}
return ret.ToString();
}
}
}
}
public static string DESDecrypt(this string value, string key)
{
using (var des = new DESCryptoServiceProvider())
{
var len = value.Length / 2;
byte[] inputByteArray = new byte[len];
for (int i = 0; i < len; i++)
{
inputByteArray[i] = (byte)Convert.ToInt32(value.Substring(i * 2, 2), 16);
}
var md5Key = key.Md5().Substring(0, 8);
des.Key = ASCIIEncoding.ASCII.GetBytes(md5Key);
des.IV = ASCIIEncoding.ASCII.GetBytes(md5Key);
using (var ms = new System.IO.MemoryStream())
{
CryptoStream cs = new CryptoStream(ms, des.CreateDecryptor(), CryptoStreamMode.Write);
cs.Write(inputByteArray, 0, inputByteArray.Length);
cs.FlushFinalBlock();
return Encoding.Default.GetString(ms.ToArray());
}
}
}
} }
} }

@ -1,22 +1,19 @@
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Infrastructure.Extensions; using Infrastructure.Extensions;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Newtonsoft.Json; using Newtonsoft.Json;
using System;
using System.Security.Cryptography;
using System.Text;
namespace Infrastructure.Security namespace Infrastructure.Security
{ {
public class EncryptionService : IEncryptionService public class EncryptionService : IEncryptionService
{ {
private readonly string _key; private readonly IConfiguration _configuration;
private readonly string _iv;
public EncryptionService(IConfiguration configuration) public EncryptionService(IConfiguration configuration)
{ {
this._key = configuration.GetSection("security").GetValue<string>("key"); this._configuration = configuration;
this._iv = configuration.GetSection("security").GetValue<string>("iv");
} }
public string CreatePasswordHash(string password, string saltkey) public string CreatePasswordHash(string password, string saltkey)
@ -52,35 +49,12 @@ namespace Infrastructure.Security
public string EncryptObject(object obj) public string EncryptObject(object obj)
{ {
var data = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(obj)); return JsonConvert.SerializeObject(obj).DESEncrypt(this._configuration.GetSection("security").GetValue<string>("key"));
var key = Encoding.UTF8.GetBytes(this._key);
var iv = Encoding.UTF8.GetBytes(this._iv);
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, TripleDES.Create().CreateEncryptor(key, iv), CryptoStreamMode.Write))
{
cs.Write(data, 0, data.Length);
cs.FlushFinalBlock();
}
return ms.ToArray().BytesToHex();
}
} }
public T DecryptObject<T>(string value) public T DecryptObject<T>(string value)
{ {
var data = value.HexToBytes(); return JsonConvert.DeserializeObject<T>(value.DESDecrypt(this._configuration.GetSection("security").GetValue<string>("key")));
var key = Encoding.UTF8.GetBytes(this._key);
var iv = Encoding.UTF8.GetBytes(this._iv);
using (var ms = new MemoryStream(data))
{
using (var cs = new CryptoStream(ms, TripleDES.Create().CreateDecryptor(key, iv), CryptoStreamMode.Read))
{
using (var sr = new StreamReader(cs, Encoding.UTF8))
{
return JsonConvert.DeserializeObject<T>(sr.ReadToEnd());
}
}
}
} }
} }
} }

@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using System;
namespace Infrastructure.Web namespace Infrastructure.Web
{ {
@ -17,18 +18,60 @@ namespace Infrastructure.Web
public override void OnResultExecuting(ResultExecutingContext context) public override void OnResultExecuting(ResultExecutingContext context)
{ {
var config = context.HttpContext.RequestServices.GetService<IConfiguration>(); var result = true;
if (config["id"] != DeviceId.Md5().Base64UrlEncode().Md5()) var message = "";
var sn = DeviceId;
try
{ {
if (!context.HttpContext.Request.Path.Value.StartsWith("/App/")) var config = context.HttpContext.RequestServices.GetService<IConfiguration>();
var hashCode = config["code"];
var code = hashCode.DESDecrypt(sn);
var values = code.Split('-');
if (sn != values[0])
{ {
context.Result = new RedirectResult("/Admin/Configuration"); message = $"授权码不匹配当前设备{sn}";
result = false;
} }
else else
{ {
context.Result = new JsonResult($"设备{DeviceId}的授权码\"{config["id"]}\"异常"); var timeSeconds = Convert.ToInt64(values[1]);
if (timeSeconds != 0)
{
var endTime = DateTimeOffset.FromUnixTimeSeconds(timeSeconds);
if (endTime < DateTimeOffset.UtcNow)
{
message = $"当前设备{sn}的授权码已过期";
result = false;
}
}
} }
} }
catch (Exception ex)
{
ex.PrintStack();
message = $"当前设备{sn}的授权码无效";
result = false;
}
if (!result)
{
if (context.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest")
{
context.Result = new JsonResult(new { code = 1, message = message });
}
else
{
var queryString = System.Web.HttpUtility.ParseQueryString(string.Empty);
queryString["message"] = message;
queryString["returnUrl"] = "/Admin/Configuration";
var url = $"/Admin/Configuration/RedirectTo?{queryString.ToString()}";
context.Result = new RedirectResult(url);
}
}
}
private string GetCode(string deviceId)
{
return DeviceId.Base64UrlEncode().Md5();
} }
} }
} }

@ -11,7 +11,7 @@ namespace Infrastructure.Web
public IActionResult RedirectTo(string action = "Index", string controller = null, string message = "操作成功,正在为您跳转", object routeValues = null, string returnUrl = null) public IActionResult RedirectTo(string action = "Index", string controller = null, string message = "操作成功,正在为您跳转", object routeValues = null, string returnUrl = null)
{ {
ViewBag.Message = message; ViewBag.Message = message;
ViewBag.Url = string.IsNullOrEmpty(returnUrl) ? Url.Action(action, controller, routeValues) : returnUrl; ViewBag.Url = string.IsNullOrEmpty(returnUrl) ? Url.Action("Index", controller, routeValues) : returnUrl;
return View("Redirect"); return View("Redirect");
} }

@ -7,20 +7,28 @@ namespace IdGen
{ {
private static void Main(string[] args) private static void Main(string[] args)
{ {
if (args.Length > 0) Console.WriteLine("物联网设备程序授权码生成工具:");
{ var message = "请输入以空格分隔的设备编号、有效天数有效天数为0则不限制使用期限";
Console.WriteLine(args[0].Md5().Base64UrlEncode().Md5()); Console.WriteLine(message);
}
while (true) while (true)
{ {
var input = Console.ReadLine(); var input = Console.ReadLine();
if (input == "q") try
{ {
break; if (input == "q")
{
break;
}
var values = input.Split(' ');
var sn = values[0];
var days = Convert.ToInt32(values[1]);
var endTime = days == 0 ? 0 : new DateTimeOffset(DateTime.UtcNow.Date.AddDays(days + 1).AddSeconds(-1)).ToUnixTimeMilliseconds();
Console.WriteLine($"{sn}-{endTime}".DESEncrypt(sn));
} }
else catch (Exception ex)
{ {
Console.WriteLine(input.Md5().Base64UrlEncode().Md5()); Console.WriteLine(ex.Message);
Console.WriteLine(message);
} }
} }
} }

@ -176,7 +176,7 @@ namespace IoT.Shared.DeviceServices.FBee
ProductId = product.Id, ProductId = product.Id,
NodeId = node.Id, NodeId = node.Id,
}; };
device.ConnectId = this._configuration["connectId"]; device.ConnectId = this._configuration["sn"];
deviceRepo.Add(device); deviceRepo.Add(device);
} }
device.Ip = ip; device.Ip = ip;
@ -546,7 +546,7 @@ namespace IoT.Shared.DeviceServices.FBee
} }
} }
device.IsOnline = isOnline != 0x00; device.IsOnline = isOnline != 0x00;
device.ConnectId = this._configuration["connectId"]; device.ConnectId = this._configuration["sn"];
device.AddorUpdateData(device.CreateData(Keys.DeviceId, deviceId, DeviceDataType.Int, Keys.DeviceId, hidden: true)); device.AddorUpdateData(device.CreateData(Keys.DeviceId, deviceId, DeviceDataType.Int, Keys.DeviceId, hidden: true));
device.AddorUpdateData(device.CreateData(Keys.Address, address, DeviceDataType.String, Keys.Address, hidden: true)); device.AddorUpdateData(device.CreateData(Keys.Address, address, DeviceDataType.String, Keys.Address, hidden: true));

@ -142,7 +142,7 @@ namespace IoT.Shared.DeviceServices.Onvif
device.AddorUpdateData(device.CreateData("subrtmp", $"rtmp://{this._configuration["stream.rtmp"]}/live/sub{ipCamera.Id}", DeviceDataType.String, "子码流rtmp")); device.AddorUpdateData(device.CreateData("subrtmp", $"rtmp://{this._configuration["stream.rtmp"]}/live/sub{ipCamera.Id}", DeviceDataType.String, "子码流rtmp"));
device.AddorUpdateData(device.CreateData("subflv", $"http://{this._configuration["stream.flv"]}/live/sub{ipCamera.Id}.flv", DeviceDataType.String, "子码流flv")); device.AddorUpdateData(device.CreateData("subflv", $"http://{this._configuration["stream.flv"]}/live/sub{ipCamera.Id}.flv", DeviceDataType.String, "子码流flv"));
device.AddorUpdateData(device.CreateData("subhls", $"http://{this._configuration["stream.hls"]}/live/sub{ipCamera.Id}.m3u8", DeviceDataType.String, "子码流hls")); device.AddorUpdateData(device.CreateData("subhls", $"http://{this._configuration["stream.hls"]}/live/sub{ipCamera.Id}.m3u8", DeviceDataType.String, "子码流hls"));
device.ConnectId = this._configuration["connectId"]; device.ConnectId = this._configuration["sn"];
deviceRepo.Add(device); deviceRepo.Add(device);
deviceRepo.SaveChanges(); deviceRepo.SaveChanges();
} }

@ -114,7 +114,7 @@ namespace IoT.Shared.Infrastructure
private void InitConnection() private void InitConnection()
{ {
this._notifyHost = this._cfg["notify:host"]; this._notifyHost = this._cfg["notify:host"];
var url = $"http://{this._notifyHost}/hub?group={this._cfg["connectId"]}"; var url = $"http://{this._notifyHost}/hub?group={this._cfg["sn"]}";
if (this.Connection != null) if (this.Connection != null)
{ {
this.Connection.DisposeAsync(); this.Connection.DisposeAsync();

@ -81,7 +81,7 @@ namespace IoT.Shared.Infrastructure
{ {
if (method == Methods.HealthCheckRequest) if (method == Methods.HealthCheckRequest)
{ {
this.ClientToServer(Methods.HealthCheckResponse, this._cfg["node.number"]); this.ClientToServer(Methods.HealthCheckResponse, this._cfg["sn"]);
} }
else if (method == Methods.GetProductRequest) else if (method == Methods.GetProductRequest)
{ {
@ -103,7 +103,7 @@ namespace IoT.Shared.Infrastructure
var url = $"http://localhost:{port}{message}"; var url = $"http://localhost:{port}{message}";
var httpClient = scope.ServiceProvider.GetService<IHttpClientFactory>().CreateClient(); var httpClient = scope.ServiceProvider.GetService<IHttpClientFactory>().CreateClient();
var result = httpClient.GetStringAsync(url).Result; var result = httpClient.GetStringAsync(url).Result;
this.Connection.SendAsync(Methods.ClientToServer, Methods.ApiCallback, result, cfg["connectId"]); this.Connection.SendAsync(Methods.ClientToServer, Methods.ApiCallback, result, cfg["sn"]);
} }
else if (method == Methods.CallScene) else if (method == Methods.CallScene)
{ {

@ -15,14 +15,15 @@ namespace IoTNode
Console.OutputEncoding = System.Text.Encoding.UTF8; Console.OutputEncoding = System.Text.Encoding.UTF8;
var host = "localhost"; var host = "localhost";
var stream = "192.168.3.124"; var stream = "192.168.3.124";
var cpuNumber = Helper.Instance.GetCPUNumber();
WebHost.CreateDefaultBuilder(args) WebHost.CreateDefaultBuilder(args)
.Run<Startup>(new List<EFConfigurationValue> { .Run<Startup>(new List<EFConfigurationValue> {
new EFConfigurationValue { Id = "id", Value= "根据设备编号生成的授权码" }, new EFConfigurationValue { Id = "sn", Value= cpuNumber },
new EFConfigurationValue { Id = "code", Value= "根据设备编号生成的授权码" },
new EFConfigurationValue { Id = "openapi.name", Value= "v1" }, new EFConfigurationValue { Id = "openapi.name", Value= "v1" },
new EFConfigurationValue { Id = "openapi.title", Value= "fbee api" }, new EFConfigurationValue { Id = "openapi.title", Value= "fbee api" },
new EFConfigurationValue { Id = "openapi.version", Value= "1.0" }, new EFConfigurationValue { Id = "openapi.version", Value= "1.0" },
new EFConfigurationValue { Id = "security:key", Value= "111111111111111111111111"}, new EFConfigurationValue { Id = "security:key", Value= "111111111111111111111111"},
new EFConfigurationValue { Id = "security:iv", Value= "11111111"},
new EFConfigurationValue { Id = "jwt:key", Value= "111111111111111111111111"}, new EFConfigurationValue { Id = "jwt:key", Value= "111111111111111111111111"},
new EFConfigurationValue { Id = "email:host", Value= "nbaxp.com"}, new EFConfigurationValue { Id = "email:host", Value= "nbaxp.com"},
new EFConfigurationValue { Id = "email:port", Value= "25"}, new EFConfigurationValue { Id = "email:port", Value= "25"},
@ -33,8 +34,6 @@ namespace IoTNode
new EFConfigurationValue { Id = "notify:enabled", Value= "true"}, new EFConfigurationValue { Id = "notify:enabled", Value= "true"},
new EFConfigurationValue { Id = "notify:host", Value= $"{host}:8001"}, new EFConfigurationValue { Id = "notify:host", Value= $"{host}:8001"},
new EFConfigurationValue { Id = "timer.seconds", Value="60"}, new EFConfigurationValue { Id = "timer.seconds", Value="60"},
new EFConfigurationValue { Id = "connectId", Value= Helper.Instance.GetCPUNumber() },
new EFConfigurationValue { Id = "node.number", Value= Helper.Instance.GetCPUNumber() },
new EFConfigurationValue { Id = "influxdb:url", Value= "http://localhost:8086"}, new EFConfigurationValue { Id = "influxdb:url", Value= "http://localhost:8086"},
new EFConfigurationValue { Id = "influxdb:usr", Value= "admin"}, new EFConfigurationValue { Id = "influxdb:usr", Value= "admin"},
new EFConfigurationValue { Id = "influxdb:pwd", Value= "admin"}, new EFConfigurationValue { Id = "influxdb:pwd", Value= "admin"},

@ -2,6 +2,7 @@ using Application.Domain.Entities;
using Infrastructure.Data; using Infrastructure.Data;
using Infrastructure.Domain; using Infrastructure.Domain;
using Infrastructure.Extensions; using Infrastructure.Extensions;
using Infrastructure.Web;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -42,7 +43,7 @@ namespace IoTCenter.Controllers
this._eventPublisher = eventPublisher; this._eventPublisher = eventPublisher;
} }
[Authorize] [Authorize, Device]
public IActionResult Index() public IActionResult Index()
{ {
//this._eventPublisher.Publish(new EntityInsertedEvent<Node>(new Node())); //this._eventPublisher.Publish(new EntityInsertedEvent<Node>(new Node()));

@ -1,5 +1,6 @@
using Infrastructure.Application; using Infrastructure.Application;
using Infrastructure.Configuration; using Infrastructure.Configuration;
using Infrastructure.Extensions;
using Infrastructure.Web.Hosting; using Infrastructure.Web.Hosting;
using Microsoft.AspNetCore; using Microsoft.AspNetCore;
using System; using System;
@ -13,14 +14,16 @@ namespace IoTCenter
{ {
Console.OutputEncoding = System.Text.Encoding.UTF8; Console.OutputEncoding = System.Text.Encoding.UTF8;
var host = "localhost"; var host = "localhost";
var cpuNumber = Helper.Instance.GetCPUNumber();
WebHost.CreateDefaultBuilder(args) WebHost.CreateDefaultBuilder(args)
.Run<Startup>(new List<EFConfigurationValue> { .Run<Startup>(new List<EFConfigurationValue> {
new EFConfigurationValue { Id = "sn", Value= cpuNumber },
new EFConfigurationValue { Id = "code", Value= "根据设备编号生成的授权码" },
new EFConfigurationValue { Id = "openapi.name", Value= "v1" }, new EFConfigurationValue { Id = "openapi.name", Value= "v1" },
new EFConfigurationValue { Id = "openapi.title", Value= "web api" }, new EFConfigurationValue { Id = "openapi.title", Value= "web api" },
new EFConfigurationValue { Id = "openapi.version", Value= "1.0" }, new EFConfigurationValue { Id = "openapi.version", Value= "1.0" },
new EFConfigurationValue { Id = "server.urls", Value= "http://*:8001" }, new EFConfigurationValue { Id = "server.urls", Value= "http://*:8001" },
new EFConfigurationValue { Id = "security:key", Value= "111111111111111111111111"}, new EFConfigurationValue { Id = "security:key", Value= "111111111111111111111111"},
new EFConfigurationValue { Id = "security:iv", Value= "11111111"},
new EFConfigurationValue { Id = "jwt:key", Value= "111111111111111111111111"}, new EFConfigurationValue { Id = "jwt:key", Value= "111111111111111111111111"},
new EFConfigurationValue { Id = "usercenter:key", Value= "123456"}, new EFConfigurationValue { Id = "usercenter:key", Value= "123456"},
new EFConfigurationValue { Id = "usercenter:login", Value= $"http://{host}:8000/Account/Login"}, new EFConfigurationValue { Id = "usercenter:login", Value= $"http://{host}:8000/Account/Login"},

@ -17,7 +17,6 @@ namespace UserCenter
new EFConfigurationValue { Id = "openapi.version", Value= "1.0" }, new EFConfigurationValue { Id = "openapi.version", Value= "1.0" },
new EFConfigurationValue { Id = "server.urls", Value= "http://*:8000" }, new EFConfigurationValue { Id = "server.urls", Value= "http://*:8000" },
new EFConfigurationValue { Id = "security:key", Value= "111111111111111111111111"}, new EFConfigurationValue { Id = "security:key", Value= "111111111111111111111111"},
new EFConfigurationValue { Id = "security:iv", Value= "11111111"},
new EFConfigurationValue { Id = "jwt:key", Value= "111111111111111111111111"}, new EFConfigurationValue { Id = "jwt:key", Value= "111111111111111111111111"},
new EFConfigurationValue { Id = "email:host", Value= "nbaxp.com"}, new EFConfigurationValue { Id = "email:host", Value= "nbaxp.com"},
new EFConfigurationValue { Id = "email:port", Value= "25"}, new EFConfigurationValue { Id = "email:port", Value= "25"},

Loading…
Cancel
Save