0%

第一种方案 :

DAO层的函数方法 

Public User selectUser(String name,String area);

对应的Mapper.xml

<select id=“selectUser” resultMap=“BaseResultMap” parameterType=“java.lang.String”> select * from user_user_t where user_name = #{0} and user_area=#{1} </select>

其中,#{0}代表接收的是dao层中的第一个参数,#{1}代表dao层中第二参数,更多参数一致往后加即可。

第二种方案:

此方法采用Map传多参数.

Dao层的函数方法

Public User selectUser(Map paramMap);

对应的Mapper.xml

<select id=“ selectUser” resultMap=“BaseResultMap”> select * from user_user_t where user_name = #{userName,jdbcType=VARCHAR} and user_area=#{userArea,jdbcType=VARCHAR} </select>

Service层调用

Private User xxxSelectUser(){
Map paramMap=new hashMap();
paramMap.put(“userName”,”对应具体的参数值”);
paramMap.put(“userArea”,”对应具体的参数值”);
User user=xxx. selectUser(paramMap);}

个人认为此方法不够直观,见到接口方法不能直接的知道要传的参数是什么。

第三种方案:

Dao层的函数方法

Public User selectUser(@param(“userName”)Stringname,@param(“userArea”)String area);

对应的Mapper.xml

<select id=“ selectUser” resultMap=“BaseResultMap”> select * from user_user_t where user_name = #{userName,jdbcType=VARCHAR} and user_area=#{userArea,jdbcType=VARCHAR} </select>

个人觉得这种方法比较好,能让开发者看到dao层方法就知道该传什么样的参数,比较直观,个人推荐用此种方案。

复制代码

/** * */
package com.xdw.dao; import java.util.List; import com.xdw.model.Category; /** * @author xiadewang
*2018年4月16日 */
public interface CategoryDao {
List getCategoryList();
}

复制代码

复制代码

<mapper namespace=“com.xdw.dao.CategoryDao”>





<resultMap type=“Category” id=“categoryTree”>
<result column=“cid” property=“cid” javaType=“java.lang.String” />
<result column=“cname” property=“cname” javaType=“java.lang.String” />
<result column=“pid” property=“pid” javaType=“java.lang.String” />
<collection column=“cid” property=“childrenList” ofType=“Category” javaType=“java.util.ArrayList” select=“selectCategoryChildrenByCid”/>
</resultMap>

<!-- 先查询菜单根级目录 \-->
<!-- 这里的返回结果必须为resultMap,并且值为上面构建的resultMap的id的值 \-->
<select id\="getCategoryList" resultMap\="categoryTree"\> select \* from category where pid = 'root' </select\>

<!-- 再利用上次查询结果colliection中column的值cid做递归查询,查出所有子菜单 \-->
<!-- 这里的返回结果必须为resultMap,并且值为上面构建的resultMap的id的值 \-->
<select id\="selectCategoryChildrenByCid" resultMap\="categoryTree" parameterType\="String"\> select \* from category where pid = #{cid} </select\>

</mapper>

复制代码

复制代码

/** * */
package com.xdw.service; import java.util.List; import com.xdw.model.Category; /** * @author xiadewang
*2018年4月16日 */
public interface CategoryService {
List getCategoryList();
} /** * */
package com.xdw.service.impl; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.xdw.dao.CategoryDao; import com.xdw.model.Category; import com.xdw.service.CategoryService; /** * @author xiadewang
*2018年4月16日 */ @Service public class CategoryServiceImpl implements CategoryService {
@Autowired private CategoryDao categoryDao; /* (non-Javadoc)
* @see com.xdw.service.CategoryService#getCategoryList() */ @Override public List getCategoryList() { // TODO Auto-generated method stub
return categoryDao.getCategoryList();
}

}

复制代码

@RequestMapping(“/getCategoryTree”)
@ResponseBody public List getCategoryTree() { return categoryService.getCategoryList();
}

此时基于SSM的操作已经完毕,核心就是在写mybatis的xml,可以在浏览器中查看运行结果,返回的是json数据。运行结果如下

复制代码

技术交流加微信
如果表A的主关键字是表B中的字段,则该字段称为表B的外键,表A称为主表,表B称为从表。外键是用来实现参照完整性的,不同的外键约束方式将可以使两张表紧密的结合起来,特别是修改或者删除的级联操作将使得日常的维护工作更加轻松。外键主要用来保证数据的完整性和一致性

两个表必须是InnoDB表,MyISAM表暂时不支持外键
外键列必须建立了索引,MySQL 4.1.2以后的版本在建立外键时会自动创建索引,但如果在较早的版本则需要显示建立;
外键关系的两个表的列必须是数据类型相似,也就是可以相互转换类型的列,比如int和tinyint可以,而int和char则不可以;

创建外键语法:
代码如下 复制代码

[CONSTRAINT [symbol]] FOREIGN KEY
[index_name] (index_col_name, …)
REFERENCES tbl_name (index_col_name,…)
[ON DELETE reference_option]
[ON UPDATE reference_option]

reference_option:
RESTRICT | CASCADE | SET NULL | NO ACTION

如果子表试图创建一个在父表中不存在的外键值,InnoDB会拒绝任何INSERT或UPDATE操作。如果父表试图UPDATE或者DELETE任何子表中存在或匹配的外键值,最终动作取决于外键约束定义中的ON UPDATE和ON DELETE选项。InnoDB支持5种不同的动作,如果没有指定ON DELETE或者ON UPDATE,默认的动作为RESTRICT: 1. CASCADE: 从父表中删除或更新对应的行,同时自动的删除或更新自表中匹配的行。ON DELETE CANSCADE和ON UPDATE CANSCADE都被InnoDB所支持。 2. SET NULL: 从父表中删除或更新对应的行,同时将子表中的外键列设为空。注意,这些在外键列没有被设为NOT NULL时才有效。ON DELETE SET NULL和ON UPDATE SET SET NULL都被InnoDB所支持。 3. NO ACTION: InnoDB拒绝删除或者更新父表。 4. RESTRICT: 拒绝删除或者更新父表。指定RESTRICT(或者NO ACTION)和忽略ON DELETE或者ON UPDATE选项的效果是一样的。 5. SET DEFAULT: InnoDB目前不支持。

外键约束使用最多的两种情况: 1)父表更新时子表也更新,父表删除时如果子表有匹配的项,删除失败; 2)父表更新时子表也更新,父表删除时子表匹配的项也删除。

前一种情况,在外键定义中,我们使用ON UPDATE CASCADE ON DELETE RESTRICT;
后一种情况,可以使用ON UPDATE CASCADE ON DELETE CASCADE。
使用案例:
(1)创建表:
代码如下 复制代码 --
http://www.111cn.net 原创
– CREATE TABLE IF NOT EXISTS `article` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`category_id` int(11) NOT NULL,
`name` char(16) NOT NULL,
PRIMARY KEY (`id`),
KEY `fk_1` (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;

INSERT INTO `article` (`id`, `category_id`, `name`) VALUES
(1, 1, ‘文章1’);

CREATE TABLE IF NOT EXISTS `category` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` char(16) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;

INSERT INTO `category` (`id`, `name`) VALUES
(1, ‘分类1’);

创建外键约束:
代码如下 复制代码

ALTER TABLE `article`
ADD CONSTRAINT `fk_1` FOREIGN KEY (`category_id`) REFERENCES `category` (`id`);

(2)删除主表category中数据:delete FROM `category` WHERE id=1,会报错:
#1451 - Cannot delete or update a parent row: a foreign key constraint fails (`test`.`article`, CONSTRAINT `fk_1` FOREIGN KEY (`category_id`) REFERENCES `category` (`id`))
(3)从表article中,添加不存在的category_id:insert into article(category_id,name) values(2,’分类2’) 会报错:
#1452 - Cannot add or update a child row: a foreign key constraint fails (`test`.`article`, CONSTRAINT `fk_1` FOREIGN KEY (`category_id`) REFERENCES `category` (`id`))

(4)更改更新删除约束
代码如下 复制代码 --删除外键
ALTER TABLE article DROP FOREIGN KEY fk_1 --添加外键
ALTER TABLE `article` ADD CONSTRAINT `fk_1` FOREIGN KEY ( `category_id` )
REFERENCES `category` ( `id` )
ON DELETE CASCADE ON UPDATE CASCADE

此时如下操作:
代码如下 复制代码 --此时article中的记录也会被删除
delete from category where id=1; --此时article中的category_id也会被更新成3
UPDATE `test`.`category` SET `id` = ‘3’ WHERE `category`.`id` =2;

复制代码

.NET Core 使用RSA算法 加密/解密/签名/验证签名 - 晓晨Master - 博客园

Excerpt

前言 前不久移植了支付宝官方的SDK,以适用ASP.NET Core使用支付宝支付,但是最近有好几位用户反应在Linux下使用会出错,调试发现是RSA加密的错误,下面具体讲一讲。 RSA在.NET Core的改动 以前我们使用RSA加密主要是使用 这个类,在.NET Core中也有这个类,但是这个类


前言

前不久移植了支付宝官方的SDK,以适用ASP.NET Core使用支付宝支付,但是最近有好几位用户反应在Linux下使用会出错,调试发现是RSA加密的错误,下面具体讲一讲。

RSA在.NET Core的改动

以前我们使用RSA加密主要是使用RSACryptoServiceProvider这个类,在.NET Core中也有这个类,但是这个类并不支持跨平台,所以如果你是用这个类来进行加/解密在windows上运行是完全没有错误的,但是只要你一放到Linux下就会出现异常。

查阅资料得知,要解决这个问题,需要改用 System.Security.Cryptography.RSA.Create() 工厂方法,使用它之后,在 Windows 上创建的是 System.Security.Cryptography.RSACng 的实例,在 Mac 与 Linux 上创建的是 System.Security.Cryptography.RSAOpenSsl 的实例,它们都继承自 System.Security.Cryptography.RSA 抽象类。

RSACng:#

相关资料:https://docs.microsoft.com/zh-cn/dotnet/api/system.security.cryptography.rsacng?view=netcore-2.0

RSAOpenSsl :#

相关资料:https://docs.microsoft.com/zh-cn/dotnet/api/system.security.cryptography.rsaopenssl?view=netcore-2.0

在Windows上的调试截图:#

在Mac上使用Visual studio For Mac 调试截图:#

RSA公钥/私钥说明

这里的RSA加密/解密主要是针对于由OpenSSL生成的公钥/私钥字符串。ssh-keygen -t rsa 命令生成的公钥私钥是不行的。

公钥示例:#

1
-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7PyjMEuniN6BPn8oqzIZ6AO1N jSTO9R3adCCIwKfKIEoWXXM+tHDpktdPKSaAsWJPTNAGvEvtxOfzXib/EMXKqD0e Uy5MatfpRjRdf1hJVimmfrb09Qx2j7CsKLy7nD23m4xubdYBwvkjMwt/L3JxB5D6 qryW1wei/j1c+/OCxQIDAQAB -----END PUBLIC KEY-----

私钥示例:#

1
-----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQC7PyjMEuniN6BPn8oqzIZ6AO1NjSTO9R3adCCIwKfKIEoWXXM+ tHDpktdPKSaAsWJPTNAGvEvtxOfzXib/EMXKqD0eUy5MatfpRjRdf1hJVimmfrb0 9Qx2j7CsKLy7nD23m4xubdYBwvkjMwt/L3JxB5D6qryW1wei/j1c+/OCxQIDAQAB AoGAT7vGYJgRNf4f6qgNS4pKHTu10RcwPFyOOM7IZ9M5380+HyXuBB6MEjowKwpH 1fcy+LepwaR+5KG7b5uBGY4H2ticMtdysBd9gLwnY4Eh4j7LCWE54HvELpeWXkWp FQdb/NQhcqMAGwYsTnRPdBqkrUmJBTYqEGkIlqCQ5vUJOCECQQDhe0KGmbq1RWp6 TDvgpA2dUmlt2fdP8oNW8O7MvbDaQRduoZnVRTPYCDKfzFqpNXL1hAYgth1N0vzD nv3VoLcpAkEA1JcY+rLv5js1g5Luv8LaI5/3uOg0CW7fmh/LfGuz8k/OxASN+cAO UjPHrxtc5xn1zat4/bnV5GEdlOp/DhquPQJBAIV2Fsdi4M+AueiPjPWHRQO0jvDV jfwFOFZSn5YSRUa6NmtmPY6tumUJXSWWqKb1GwlVTuc3xBqXYsNLLUWwLhkCQQDJ UJCiD0LohhdGEqUuSKnj5H9kxddJO4pZXFSI7UEJbJQDwcBkyn+FTm2BH+tZGZdQ fVnlA89OJr0poOpSg+eNAkAKY85SR9KASaTiDBoPpJ8N805XEhd0Kq+ghzSThxL3 fVtKUQLiCh7Yd8oMd/G5S3xWJHUXSioATT8uPRH2bOb/ -----END RSA PRIVATE KEY-----

公钥/私钥生成

Windows&MAC_OSX可以使用有支付宝开发的RSA密钥生成工具:

使用此工具生成的时候一定要选择,PKCS1

下载地址:https://doc.open.alipay.com/docs/doc.htm?treeId=291&articleId=105971&docType=1

此外还可以使用OpenSSL工具命令来生成:https://doc.open.alipay.com/docs/doc.htm?articleId=106130&docType=1

.NET Core 中的使用

这里要讲一下RSA2算法。

什么是RSA2 ?RSA2 是在原来SHA1WithRSA签名算法的基础上,新增了支持SHA256WithRSA的签名算法。该算法比SHA1WithRSA有更强的安全能力。

算法名称 标准签名算法名称 备注
RSA2 SHA256WithRSA (强烈推荐使用),强制要求RSA密钥的长度至少为2048
RSA SHA1WithRSA 对RSA密钥的长度不限制,推荐使用2048位以上

签名的作用:保证数据完整性,机密性和发送方角色的不可抵赖性

这里来一发干货,我已经封装好的RSA/RSA2算法,支持加密/解密/签名/验证签名。

1
/// <summary> /// RSA加解密 使用OpenSSL的公钥加密/私钥解密 /// 作者:李志强 /// 创建时间:2017年10月30日15:50:14 /// QQ:501232752 /// </summary> public class RSAHelper { private readonly RSA _privateKeyRsaProvider; private readonly RSA _publicKeyRsaProvider; private readonly HashAlgorithmName _hashAlgorithmName; private readonly Encoding _encoding; /// <summary> /// 实例化RSAHelper /// </summary> /// <param name="rsaType">加密算法类型 RSA SHA1;RSA2 SHA256 密钥长度至少为2048</param> /// <param name="encoding">编码类型</param> /// <param name="privateKey">私钥</param> /// <param name="publicKey">公钥</param> public RSAHelper(RSAType rsaType, Encoding encoding, string privateKey, string publicKey = null) { _encoding = encoding; if (!string.IsNullOrEmpty(privateKey)) { _privateKeyRsaProvider = CreateRsaProviderFromPrivateKey(privateKey); } if (!string.IsNullOrEmpty(publicKey)) { _publicKeyRsaProvider = CreateRsaProviderFromPublicKey(publicKey); } _hashAlgorithmName = rsaType == RSAType.RSA ? HashAlgorithmName.SHA1 : HashAlgorithmName.SHA256; } #region 使用私钥签名 /// <summary> /// 使用私钥签名 /// </summary> /// <param name="data">原始数据</param> /// <returns></returns> public string Sign(string data) { byte[] dataBytes = _encoding.GetBytes(data); var signatureBytes = _privateKeyRsaProvider.SignData(dataBytes, _hashAlgorithmName, RSASignaturePadding.Pkcs1); return Convert.ToBase64String(signatureBytes); } #endregion #region 使用公钥验证签名 /// <summary> /// 使用公钥验证签名 /// </summary> /// <param name="data">原始数据</param> /// <param name="sign">签名</param> /// <returns></returns> public bool Verify(string data,string sign) { byte[] dataBytes = _encoding.GetBytes(data); byte[] signBytes = Convert.FromBase64String(sign); var verify = _publicKeyRsaProvider.VerifyData(dataBytes, signBytes, _hashAlgorithmName, RSASignaturePadding.Pkcs1); return verify; } #endregion #region 解密 public string Decrypt(string cipherText) { if (_privateKeyRsaProvider == null) { throw new Exception("_privateKeyRsaProvider is null"); } return Encoding.UTF8.GetString(_privateKeyRsaProvider.Decrypt(Convert.FromBase64String(cipherText), RSAEncryptionPadding.Pkcs1)); } #endregion #region 加密 public string Encrypt(string text) { if (_publicKeyRsaProvider == null) { throw new Exception("_publicKeyRsaProvider is null"); } return Convert.ToBase64String(_publicKeyRsaProvider.Encrypt(Encoding.UTF8.GetBytes(text), RSAEncryptionPadding.Pkcs1)); } #endregion #region 使用私钥创建RSA实例 public RSA CreateRsaProviderFromPrivateKey(string privateKey) { var privateKeyBits = Convert.FromBase64String(privateKey); var rsa = RSA.Create(); var rsaParameters = new RSAParameters(); using (BinaryReader binr = new BinaryReader(new MemoryStream(privateKeyBits))) { byte bt = 0; ushort twobytes = 0; twobytes = binr.ReadUInt16(); if (twobytes == 0x8130) binr.ReadByte(); else if (twobytes == 0x8230) binr.ReadInt16(); else throw new Exception("Unexpected value read binr.ReadUInt16()"); twobytes = binr.ReadUInt16(); if (twobytes != 0x0102) throw new Exception("Unexpected version"); bt = binr.ReadByte(); if (bt != 0x00) throw new Exception("Unexpected value read binr.ReadByte()"); rsaParameters.Modulus = binr.ReadBytes(GetIntegerSize(binr)); rsaParameters.Exponent = binr.ReadBytes(GetIntegerSize(binr)); rsaParameters.D = binr.ReadBytes(GetIntegerSize(binr)); rsaParameters.P = binr.ReadBytes(GetIntegerSize(binr)); rsaParameters.Q = binr.ReadBytes(GetIntegerSize(binr)); rsaParameters.DP = binr.ReadBytes(GetIntegerSize(binr)); rsaParameters.DQ = binr.ReadBytes(GetIntegerSize(binr)); rsaParameters.InverseQ = binr.ReadBytes(GetIntegerSize(binr)); } rsa.ImportParameters(rsaParameters); return rsa; } #endregion #region 使用公钥创建RSA实例 public RSA CreateRsaProviderFromPublicKey(string publicKeyString) { // encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1" byte[] seqOid = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 }; byte[] seq = new byte[15]; var x509Key = Convert.FromBase64String(publicKeyString); // --------- Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob ------ using (MemoryStream mem = new MemoryStream(x509Key)) { using (BinaryReader binr = new BinaryReader(mem)) //wrap Memory Stream with BinaryReader for easy reading { byte bt = 0; ushort twobytes = 0; twobytes = binr.ReadUInt16(); if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81) binr.ReadByte(); //advance 1 byte else if (twobytes == 0x8230) binr.ReadInt16(); //advance 2 bytes else return null; seq = binr.ReadBytes(15); //read the Sequence OID if (!CompareBytearrays(seq, seqOid)) //make sure Sequence for OID is correct return null; twobytes = binr.ReadUInt16(); if (twobytes == 0x8103) //data read as little endian order (actual data order for Bit String is 03 81) binr.ReadByte(); //advance 1 byte else if (twobytes == 0x8203) binr.ReadInt16(); //advance 2 bytes else return null; bt = binr.ReadByte(); if (bt != 0x00) //expect null byte next return null; twobytes = binr.ReadUInt16(); if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81) binr.ReadByte(); //advance 1 byte else if (twobytes == 0x8230) binr.ReadInt16(); //advance 2 bytes else return null; twobytes = binr.ReadUInt16(); byte lowbyte = 0x00; byte highbyte = 0x00; if (twobytes == 0x8102) //data read as little endian order (actual data order for Integer is 02 81) lowbyte = binr.ReadByte(); // read next bytes which is bytes in modulus else if (twobytes == 0x8202) { highbyte = binr.ReadByte(); //advance 2 bytes lowbyte = binr.ReadByte(); } else return null; byte[] modint = { lowbyte, highbyte, 0x00, 0x00 }; //reverse byte order since asn.1 key uses big endian order int modsize = BitConverter.ToInt32(modint, 0); int firstbyte = binr.PeekChar(); if (firstbyte == 0x00) { //if first byte (highest order) of modulus is zero, don't include it binr.ReadByte(); //skip this null byte modsize -= 1; //reduce modulus buffer size by 1 } byte[] modulus = binr.ReadBytes(modsize); //read the modulus bytes if (binr.ReadByte() != 0x02) //expect an Integer for the exponent data return null; int expbytes = (int)binr.ReadByte(); // should only need one byte for actual exponent data (for all useful values) byte[] exponent = binr.ReadBytes(expbytes); // ------- create RSACryptoServiceProvider instance and initialize with public key ----- var rsa = RSA.Create(); RSAParameters rsaKeyInfo = new RSAParameters { Modulus = modulus, Exponent = exponent }; rsa.ImportParameters(rsaKeyInfo); return rsa; } } } #endregion #region 导入密钥算法 private int GetIntegerSize(BinaryReader binr) { byte bt = 0; int count = 0; bt = binr.ReadByte(); if (bt != 0x02) return 0; bt = binr.ReadByte(); if (bt == 0x81) count = binr.ReadByte(); else if (bt == 0x82) { var highbyte = binr.ReadByte(); var lowbyte = binr.ReadByte(); byte[] modint = { lowbyte, highbyte, 0x00, 0x00 }; count = BitConverter.ToInt32(modint, 0); } else { count = bt; } while (binr.ReadByte() == 0x00) { count -= 1; } binr.BaseStream.Seek(-1, SeekOrigin.Current); return count; } private bool CompareBytearrays(byte[] a, byte[] b) { if (a.Length != b.Length) return false; int i = 0; foreach (byte c in a) { if (c != b[i]) return false; i++; } return true; } #endregion } /// <summary> /// RSA算法类型 /// </summary> public enum RSAType { /// <summary> /// SHA1 /// </summary> RSA = 0, /// <summary> /// RSA2 密钥长度至少为2048 /// SHA256 /// </summary> RSA2 }

使用:#

1
static void Main(string[] args) { //2048 公钥 string publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoQh0wEqx/R2H1v00IU12Oc30fosRC/frhH89L6G+fzeaqI19MYQhEPMU13wpeqRONCUta+2iC1sgCNQ9qGGf19yGdZUfueaB1Nu9rdueQKXgVurGHJ+5N71UFm+OP1XcnFUCK4wT5d7ZIifXxuqLehP9Ts6sNjhVfa+yU+VjF5HoIe69OJEPo7OxRZcRTe17khc93Ic+PfyqswQJJlY/bgpcLJQnM+QuHmxNtF7/FpAx9YEQsShsGpVo7JaKgLo+s6AFoJ4QldQKir2vbN9vcKRbG3piElPilWDpjXQkOJZhUloh/jd7QrKFimZFldJ1r6Q59QYUyGKZARUe0KZpMQIDAQAB"; //2048 私钥 string privateKey = "MIIEpAIBAAKCAQEAoQh0wEqx/R2H1v00IU12Oc30fosRC/frhH89L6G+fzeaqI19MYQhEPMU13wpeqRONCUta+2iC1sgCNQ9qGGf19yGdZUfueaB1Nu9rdueQKXgVurGHJ+5N71UFm+OP1XcnFUCK4wT5d7ZIifXxuqLehP9Ts6sNjhVfa+yU+VjF5HoIe69OJEPo7OxRZcRTe17khc93Ic+PfyqswQJJlY/bgpcLJQnM+QuHmxNtF7/FpAx9YEQsShsGpVo7JaKgLo+s6AFoJ4QldQKir2vbN9vcKRbG3piElPilWDpjXQkOJZhUloh/jd7QrKFimZFldJ1r6Q59QYUyGKZARUe0KZpMQIDAQABAoIBAQCRZLUlOUvjIVqYvhznRK1OG6p45s8JY1r+UnPIId2Bt46oSLeUkZvZVeCnfq9k0Bzb8AVGwVPhtPEDh73z3dEYcT/lwjLXAkyPB6gG5ZfI/vvC/k7JYV01+neFmktw2/FIJWjEMMF2dvLNZ/Pm4bX1Dz9SfD/45Hwr8wqrvRzvFZsj5qqOxv9RPAudOYwCwZskKp/GF+L+3Ycod1Wu98imzMZUH+L5dQuDGg3kvf3ljIAegTPoqYBg0imNPYY/EGoFKnbxlK5S5/5uAFb16dGJqAz3XQCz9Is/IWrOTu0etteqV2Ncs8uqPdjed+b0j8CMsr4U1xjwPQ8WwdaJtTkRAoGBANAndgiGZkCVcc9975/AYdgFp35W6D+hGQAZlL6DmnucUFdXbWa/x2rTSEXlkvgk9X/PxOptUYsLJkzysTgfDywZwuIXLm9B3oNmv3bVgPXsgDsvDfaHYCgz0nHK6NSrX2AeX3yO/dFuoZsuk+J+UyRigMqYj0wjmxUlqj183hinAoGBAMYMOBgF77OXRII7GAuEut/nBeh2sBrgyzR7FmJMs5kvRh6Ck8wp3ysgMvX4lxh1ep8iCw1R2cguqNATr1klOdsCTOE9RrhuvOp3JrYzuIAK6MpH/uBICy4w1rW2+gQySsHcH40r+tNaTFQ7dQ1tef//iy/IW8v8i0t+csztE1JnAoGABdtWYt8FOYP688+jUmdjWWSvVcq0NjYeMfaGTOX/DsNTL2HyXhW/Uq4nNnBDNmAz2CjMbZwt0y+5ICkj+2REVQVUinAEinTcAe5+LKXNPx4sbX3hcrJUbk0m+rSu4G0B/f5cyXBsi9wFCAzDdHgBduCepxSr04Sc9Hde1uQQi7kCgYB0U20HP0Vh+TG2RLuE2HtjVDD2L/CUeQEiXEHzjxXWnhvTg+MIAnggvpLwQwmMxkQ2ACr5sd/3YuCpB0bxV5o594nsqq9FWVYBaecFEjAGlWHSnqMoXWijwu/6X/VOTbP3VjH6G6ECT4GR4DKKpokIQrMgZ9DzaezvdOA9WesFdQKBgQCWfeOQTitRJ0NZACFUn3Fs3Rvgc9eN9YSWj4RtqkmGPMPvguWo+SKhlk3IbYjrRBc5WVOdoX8JXb2/+nAGhPCuUZckWVmZe5pMSr4EkNQdYeY8kOXGSjoTOUH34ZdKeS+e399BkBWIiXUejX/Srln0H4KoHnTWgxwNpTsBCgXu8Q=="; var rsa = new RSAHelper(RSAType.RSA2,Encoding.UTF8, privateKey, publicKey); string str = "博客园 http://www.cnblogs.com/"; Console.WriteLine("原始字符串:"+str); //加密 string enStr = rsa.Encrypt(str); Console.WriteLine("加密字符串:"+enStr); //解密 string deStr = rsa.Decrypt(enStr); Console.WriteLine("解密字符串:"+deStr); //私钥签名 string signStr = rsa.Sign(str); Console.WriteLine("字符串签名:" + signStr); //公钥验证签名 bool signVerify = rsa.Verify(str,signStr); Console.WriteLine("验证签名:" + signVerify); Console.ReadKey(); }

运行:

参考#

本文Demo:https://github.com/stulzq/DotnetCore.RSA

.NET Core 配置Configuration杂谈 - 晓晨Master - 博客园

Excerpt

前言 .NET Core 在配置文件的操作上相对于.NET Framework做了不少改变,今天来聊一聊。关于Configuration的Package都是以 开头的支持多种方式的配置,包括内存、Json文件、XML文件等等,今天我们主要用Json格式文件配置来演示。 开始 新建一个ConsoleA


前言

.NET Core 在配置文件的操作上相对于.NET Framework做了不少改变,今天来聊一聊。关于Configuration的Package都是以Microsoft.Extensions.Configuration开头的支持多种方式的配置,包括内存、Json文件、XML文件等等,今天我们主要用Json格式文件配置来演示。

开始

新建一个ConsoleApp(这里为了方便演示就用控制台程序来演示了,而不用ASP.NET Core),添加两个Package:

1
Install-Package Microsoft.Extensions.Configuration -Version 2.0.1 Install-Package Microsoft.Extensions.Configuration.Json -Version 2.0.1

添加Json配置文件,读取配置

1
var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json"); var config = builder.Build(); //读取配置 Console.WriteLine(config["Alipay:AppId"]); Console.WriteLine(config["Alipay:PriviteKey"]);

我们的Json文件内容如下:

1
{ "Alipay": { "AppId": "20185555", "PriviteKey": "dasfdafafafa" } }

我们通过ConfigurationBuilder对象来创建ConfigurationRoot对象,并用其来读取配置。SetBasePath()方法是用来设置我们配置对象需要的配置文件的基础路径,比如我们将基础路径设置为C:\ConsoleApp,那么他读取我们的配置文件appsettings.json的路径将是C:\ConsoleApp\appsettings.json

运行:

多次调用 AddJsonFile 方法会发生什么

我们新建一个appsettings.Test.json文件,添加如下内容:

1
{ "Alipay": { "AppId": "20185555Testss", "PriviteKey": "dasfdafafafaTestss" } }

然后修改一下代码:

1
var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json") .AddJsonFile("appsettings.Test.json");

可以看见我们向ConfigurationBuilder对象设置了两次Json文件,看一下运行效果:

可以得出结论:读取配置的时候会选择最后添加的文件。

那么我们前一个文件去哪了呢?我们的ConfigurationRoot对象有一个Providers属性存储了,我们添加的文件信息,我们可以遍历它:

1
foreach (var provider in config.Providers) { provider.TryGet("Alipay:AppId", out string val); Console.WriteLine(val); }

运行:

可以看出我们两个文件的值都被读取到了!

配置重载

我们的配置文件可能会被更改,那么我们如何获取最新的配置?我们在添加文件的时候可以将一个名为reloadOnChange的参数设置为true,那么当我们的文件发生更改时,就会重新载入配置到内存中来,然后我们获取到的配置就行最新的。我们读取配置的时候并不是每次读取都是从文件里面读取,而是配置文件的所有配置信息都会被加载到内存中来,我们每次从内存读取就行了。

1
var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json") .AddJsonFile("appsettings.Test.json",true,reloadOnChange:true); var config = builder.Build(); //读取配置 Console.WriteLine(config["Alipay:AppId"]); Console.WriteLine(config["Alipay:PriviteKey"]); Console.WriteLine("更改文件之后,按下任意键"); Console.ReadKey(); Console.WriteLine("change:"); Console.WriteLine(config["Alipay:AppId"]); Console.WriteLine(config["Alipay:PriviteKey"]); Console.ReadKey();

我们看一下效果:

今天就到这里了!

Demo:https://github.com/stulzq/BlogDemos/tree/master/ConfigurationTest

.NET Remoting原理及应用实例: - 跨界农民工 - 博客园

Excerpt

Remoting:(本文摘自百度百科) 简介: 什么是Remoting,简而言之,我们可以将其看作是一种分布式处理方 式。从微软的产品角度来看,可以说Remoting就是DCOM的一种升级,它改善了很多功能,并极好的融合到.Net平台下。Microsoft .NET Remoting 提供了一种允许


    如前所述,WellKnown模式在客户端创建对象时,只能调用默认的构造函数,上面的代码就说明了这一点,因为GetObject()方法不能传递构造函数的参数。而客户端激活模式则可以通过自定义的构造函数来创建远程对象。

         1) 调用RemotingConfiguration的静态方法RegisterActivatedClientType()。这个方法返回值为Void,它只是将远程对象注册在客户端而已。具体的实例化还需要调用对象类的构造函数。

          RemotingConfiguration.RegisterActivatedClientType(

          typeof(ServerRemoteObject.ServerObject),

          “tcp://localhost:8080/ServiceMessage”);

          ServerRemoteObject.ServerObject serverObj = new ServerRemoteObject.ServerObject();

   2) 调用进程Activator的CreateInstance()方法。这个方法将创建方法参数指定类型的类对象。它与前面的GetObject()不同的是,它要在客户端调用构造函数,而GetObject()只是获得对象,而创建实例是在服务器端完成的。CreateInstance()方法有很多个重载,我着重说一下其中常用的两个。

    a、 public static object CreateInstance(Type type, object[] args, object[] activationAttributes);

    参数说明:

    type:要创建的对象的类型。

    args :与要调用构造函数的参数数量、顺序和类型匹配的参数数组。如果 args 为空数组或空引用(Visual Basic 中为 Nothing),则调用不带任何参数的构造函数(默认构造函数)。

    这里的参数args是一个object[]数组类型。它可以传递要创建对象的构造函数中的参数。从这里其实可以得到一个结论:WellKnown激活模式所传递的远程对象类,只能使用默认的构造函数;而Activated模式则可以用户自定义构造函数。activationAttributes参数在这个方法中通常用来传递服务器的url。

    假设我们的远程对象类ServerObject有个构造函数:

    ServerObject(string pName,string pSex,int pAge)

    {

      name = pName;

      sex = pSex;

      age = pAge;

    }

    那么实现的代码是:

    object[] attrs = {new UrlAttribute(“tcp://localhost:8080/ServiceMessage”)};

    object[] objs = new object[3];

    objs[0] = “wayfarer”;

    objs[1] = “male”;

    objs[2] = 28;

    ServerRemoteObject.ServerObject = Activator.CreateInstance(

    typeof(ServerRemoteObject.ServerObject),objs,attrs);

    可以看到,objs[]数组传递的就是构造函数的参数。

    b、public static ObjectHandle CreateInstance(string assemblyName, string typeName, object[] activationAttribute);

    参数说明:

    assemblyName :将在其中查找名为 typeName 的类型的程序集的名称。如果 assemblyName 为空引用(Visual Basic 中为 Nothing),则搜索正在执行的程序集。

    typeName:首选类型的名称。

    activationAttributes :包含一个或多个可以参与激活的属性的数组。

    参数说明一目了然。注意这个方法返回值为ObjectHandle类型,因此代码与前不同:

    object[] attrs = {new UrlAttribute(“tcp://localhost:8080/EchoMessage”)};

    ObjectHandle handle = Activator.CreateInstance(“ServerRemoteObject”,

    “ServerRemoteObject.ServerObject”,attrs);

    ServerRemoteObject.ServerObject obj = (ServerRemoteObject.ServerObject)handle.Unwrap();

    这个方法实际上是调用的默认构造函数。ObjectHandle.Unwrap()方法是返回被包装的对象。

    说明:要使用UrlAttribute,还需要在命名空间中添加:using System.Runtime.Remoting.Activation;

基础补充:

      通过上面的描述,基本上已经完成了一个最简单的Remoting程序。这是一个标准的创建Remoting程序的方法,但在实际开发过程中,我们遇到的情况也许千奇百怪,如果只掌握一种所谓的“标准”,就妄想可以“一招鲜、吃遍天”,是不可能的。

     1、注册多个通道

     在Remoting中,允许同时创建多个通道,即根据不同的端口创建不同 的通道。但是,Remoting要求通道的名字必须不同,因为它要用来作为通道的唯一标识符。虽然IChannel有ChannelName属性,但这个 属性是只读的。因此前面所述的创建通道的方法无法实现同时注册多个通道的要求。

这个时候,我们必须用到System.Collection中的IDictionary接口:

  注册Tcp通道:

  IDictionary tcpProp = new Hashtable();

  tcpProp[“name”] = “tcp9090”;

  tcpProp[“port”] = 9090;

  IChannel channel = new TcpChannel(tcpProp,

  new BinaryClientFormatterSinkProvider(),

  new BinaryServerFormatterSinkProvider());

  ChannelServices.RegisterChannel(channel);

  注册Http通道:

  IDictionary httpProp = new Hashtable();

  httpProp[“name”] = “http8080”;

  httpProp[“port”] = 8080;

  IChannel channel = new HttpChannel(httpProp,

  new SoapClientFormatterSinkProvider(),

  new SoapServerFormatterSinkProvider());

  ChannelServices.RegisterChannel(channel);

  在name属性中,定义不同的通道名称就可以了。

  2、远程对象元数据相关性

  由于服务器端和客户端都要用到远程对象,通常的方式是生成两份完全相同的对象Dll,分别添加引用。不过为了代码的安全性,且降低客户端对远程对象元数据的相关性,我们有必要对这种方式进行改动。即在服务器端实现远程对象,而在客户端则删除这些实现的元数据。

  由于激活模式的不同,在客户端创建对象的方法也不同,所以要分离元数据的相关性,也应分为两种情况。

  (1) WellKnown激活模式:

  通过接口来实现。在服务器端,提供接口和具体类的实现,而在客户端仅提供接口:

  public interface IServerObject

  {

    Person GetPersonInfo(string name,string sex,int age);

  }

  public class ServerObject:MarshalByRefObject,IServerObject

  { ……}

*注意:两边生成该对象程序集的名字必须相同,严格地说,是命名空间的名字必须相同。

  (2) 客户端激活模式:

  如前所述,对于客户端激活模式,不管是使用静态方法,还是使用CreateInstance()方法,都必须在客户端调用构造函数实例化对象。所以,在客户端我们提供的远程对象,就不能只提供接口,而没有类的实现。实际上,要做到与远程对象元数据的分离,可以由两种方法供选择:

  a、利用WellKnown激活模式模拟客户端激活模式:

  方法是利用设计模式中的“抽象工厂”,下面的类图表描述了总体解决方案:

  我们在服务器端的远程对象中加上抽象工厂的接口和实现类:

  public interface IServerObject

  {

    Person GetPersonInfo(string name,string sex,int age);

  }

  public interface IServerObjFactory

  {

    IServerObject CreateInstance();

  }

  public class ServerObject:MarshalByRefObject,IServerObject

  {

    public Person GetPersonInfo(string name,string sex,int age)

    {

    Person person = new Person();

    person .Name = name;

    person.Sex = sex;

    person.Age = age;

    return person;

    }

  }

  public class ServerObjFactory:MarshalByRefObject,IServerObjFactory

  {

    public IServerObject CreateInstance()

    {

      return new ServerObject();

    }

  }

  然后再客户端的远程对象中只提供工厂接口和原来的对象接口:

  public interface IServerObject

  {

    Person GetPersonInfo(string name,string sex,int age);

  }

  public interface IServerObjFactory

  {

    IServerObject CreateInstance();

  }

  我们用WellKnown激活模式注册远程对象,在服务器端:

  //传递对象;

  RemotingConfiguration.RegisterWellKnownServiceType(

  typeof(ServerRemoteObject.ServerObjFactory),

  “ServiceMessage”,WellKnownObjectMode.SingleCall);

*注意这里注册的不是ServerObject类对象,而是ServerObjFactory类对象。

  客户端:

  ServerRemoteObject.IServerObjFactory serverFactory =

  (ServerRemoteObject.IServerObjFactory) Activator.GetObject(

  typeof(ServerRemoteObject.IServerObjFactory),

  “tcp://localhost:8080/ServiceMessage”);

  ServerRemoteObject.IServerObject serverObj = serverFactory.CreateInstance();

  为什么说这是一种客户端激活模式的模拟呢?从激活的方法来看,我们是使用 了SingleCall模式来激活对象,但此时激活的并非我们要传递的远程对象,而是工厂对象。如果客户端要创建远程对象,还应该通过工厂对象的 CreateInstance()方法来获得。而这个方法正是在客户端调用的。因此它的实现方式就等同于客户端激活模式。

  b、利用替代类来取代远程对象的元数据

  实际上,我们可以用一个trick,来欺骗Remoting。这里所说的替代类就是这个trick了。既然是提供服务,Remoting传递的远程对象其实现的细节当然是放在服务器端。而要在客户端放对象的副本,不过是因为客户端必须调用构造函数,而采取的无奈之举。既然具体的实现是在服务器端,又为了能在客户端实例化,那么在客户端就实现这些好了。至于实现的细节,就不用管了。

  如果远程对象有方法,服务器端则提供方法实现,而客户端就提供这个方法就 OK了,至于里面的实现,你可以是抛出一个异常,或者return 一个null值;如果方法返回void,那么里面可以是空。关键是这个客户端类对象要有这个方法。这个方法的实现,其实和方法的声明差不多,所以我说是一 个trick。方法如是,构造函数也如此。

还是用代码来说明这种“阴谋”,更直观:

  服务器端:

  public class ServerObject:MarshalByRefObject

  {

    public ServerObject()

    {

    }

    public Person GetPersonInfo(string name,string sex,int age)

    {

      Person person = new Person();

      person .Name = name;

      person.Sex = sex;

      person.Age = age;

      return person;

    }

  }

  客户端:

  public class ServerObject:MarshalByRefObject

  {

    public ServerObj()

    {

      throw new System.NotImplementedException();

    }

    public Person GetPersonInfo(string name,string sex,int age)

    {

    throw new System.NotImplementedException();

    }

  }

  比较客户端和服务器端,客户端的方法GetPersonInfo(),没有具体的实现细节,只是抛出了一个异常。或者直接写上语句return null,照样OK。我们称客户端的这个类为远程对象的替代类。

  3、利用配置文件实现

  前面所述的方法,于服务器uri、端口、以及激活模式的设置是用代码来完成的。其实我们也可以用配置文件来设置。这样做有个好处,因为这个配置文件是Xml文档。如果需要改变端口或其他,我们就不需要修改程序,并重新编译,而是只需要改变这个配置文件即可。

  (1) 服务器端的配置文件:

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
&lt;configuration&gt;
&lt;system.runtime.remoting&gt;
&lt;application name=<span>"</span><span>ServerRemoting</span><span>"</span>&gt;
&lt;service&gt;
&lt;wellknown mode=<span>"</span><span>Singleton</span><span>"</span> type=<span>"</span><span>ServerRemoteObject.ServerObject</span><span>"</span> objectUri=<span>"</span><span>ServiceMessage</span><span>"</span>/&gt;
&lt;/service&gt;
&lt;channels&gt;
&lt;channel <span>ref</span>=<span>"</span><span>tcp</span><span>"</span> port=<span>"</span><span>8080</span><span>"</span>/&gt;
&lt;/channels&gt;
&lt;/application&gt;
&lt;/system.runtime.remoting&gt;
&lt;/configuration&gt;

复制代码

  如果是客户端激活模式,则把wellknown改为activated,同时删除mode属性。

  把该配置文件放到服务器程序的应用程序文件夹中,命名为ServerRemoting.config。那么前面的服务器端程序直接用这条语句即可:

  RemotingConfiguration.Configure(“ServerRemoting.config”);

  (2)客户端配置文件

  如果是客户端激活模式,修改和上面一样。调用也是使用RemotingConfiguration.Configure()方法来调用存储在客户端的配置文件。

  配置文件还可以放在machine.config中。如果客户端程序是web应用程序,则可以放在web.config中。

  4、启动/关闭指定远程对象

  Remoting中没有提供类似 UnregisterWellKnownServiceType()的方法,也即是说,一旦通过注册了远程对象,如果没有关闭通道的话,该对象就一直存在 于通道中。只要客户端激活该对象,就会创建对象实例。如果Remoting传送的只有一个远程对象,这不存在问题,关闭通道就可以了。如果传送多个远程对 象呢?要关闭指定的远程对象应该怎么做?关闭之后又需要启动又该如何?

  我们注意到在Remoting中提供了Marshal()和 Disconnect()方法,答案就在这里。Marshal()方法是将MarshalByRefObject类对象转化为ObjRef类对象,这个对 象是存储生成代理以与远程对象通讯所需的所有相关信息。这样就可以将该实例序列化以便在应用程序域之间以及通过网络进行传输,客户端就可以调用了。而Disconnect()方法则将具体的实例对象从通道中断开。

  方法如下:

  首先注册通道:

  TcpChannel channel = new TcpChannel(8080);

  ChannelServices.RegisterChannel(channel);

  接着启动服务:

  先在服务器端实例化远程对象。

  ServerObject obj = new ServerObject();

  然后,注册该对象。注意这里不用RemotingConfiguration.RegisterWellKnownServiceType(),而是使用RemotingServices.Marshal():

  ObjRef objrefWellKnown = RemotingServices.Marshal(obj, “ServiceMessage”);

  如果要注销对象,则:

  RemotingServices.Disconnect(obj);

  要注意,这里Disconnect的类对象必须是前面实例化的对象。正因为此,我们可以根据需要创建指定的远程对象,而关闭时,则Disconnect之前实例化的对象。

至于客户端的调用,和前面WellKnown模式的方法相同,仍然是通过 Activator.GetObject()来获得。但从实现代码来看,我们会注意到一个问题,由于服务器端是显式的实例化了远程对象,因此不管客户端有 多少,是否相同,它们调用的都是同一个远程对象。因此我们将这个方法称为模拟的SingleTon模式。

  客户端激活模式

  我们也可以通过Marshal()和Disconnect()来模拟客户端激活模式。首先我们来回顾“远程对象元数据相关性”一节,在这一节中,我说到采用设计模式的“抽象工厂”来创建对象实例,以此  用SingleCall模式来模拟客户端激活模式。在仔细想想前面的模拟的SingleTon模式。是不是答案就将呼之欲出呢?

  在“模拟的SingleTon”模式中,我们是将具体的远程对象实例进行 Marshal,以此让客户端获得该对象的引用信息。那么我们换一种思路,当我们用抽象工厂提供接口,工厂类实现创建远  程对象的方法。然后我们在服务器端 创建工厂类实例。再将这个工厂类实例进行Marshal。而客户端获取对象时,不是获取具体的远程对象,而是获取具体的工厂类对象。然后再调用     CreateInstance()方法来创建具体的远程对象实例。此时,对于多个客户端而言,调用的是同一个工厂类对象;然而远程对象是在各个客户端自己 创建的,因此对于远程对象而言,则是  由客户端激活,创建的是不同对象了。

  当我们要启动/关闭指定对象时,只需要用Disconnet()方法来注销工厂类对象就可以了。

小结:

  Microsoft .Net Remoting真可以说是博大精深。整个Remoting的内容不是我这一篇小文所能尽述的,更不是我这个Remoting的初学者所能掌握的。王国维 在《人间词话》一书中写到:古今之成大事业大学问者,必经过三种境界。“昨夜西风凋碧树,独上高楼,望尽天涯路。”此第一境界也。“衣带渐宽终不悔,为伊 消得人憔悴。”此第二境界也。“众里寻他千百度,蓦然回首,那人却在灯火阑珊处。”此第三境界也。如以此来形容我对Remoting的学习,还处于“独上 高楼,望尽天涯路”的时候,真可以说还未曾登堂入室。

或许需得“衣带渐宽”,学得Remoting“终不悔”,方才可以“蓦然回首” 吧。

.NET 通过 Autofac 和 DynamicProxy 实现AOP - 晓晨Master - 博客园

Excerpt

什么是AOP?引用百度百科:AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。实现AOP主要由两种方式,一种是编译时静态植入,优点是效率高,缺点是缺乏灵活性,.net下postsharp为代表者(


  什么是AOP?引用百度百科:AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。实现AOP主要由两种方式,一种是编译时静态植入,优点是效率高,缺点是缺乏灵活性,.net下postsharp为代表者(这个是收费的)。另一种方式是动态代理,优缺点与前者相反,动态为目标类型创建代理,通过代理调用实现拦截。AOP能做什么,常见的用例是事务处理、日志记录等等。下面就讲讲Autofac怎么实现AOP,Autofac是一个.net下非常优秀,性能非常好的IOC容器(.net下效率最高的容器),加上AOP简直是如虎添翼。Autofac的AOP是通过Castle(也是一个容器)项目的核心部分实现的,名为Autofac.Extras.DynamicProxy,顾名思义,其实现方式为动态代理。

  使用前的准备:

    通过Nuget安装程序包 :Autofac、Autofac.Extras.DynamicProxy,安装成功之后会增加三个引用

    

  下面正式开始了!

  第一步:创建拦截器

  下面是一个简单的拦截器示例,该拦截器的功能是显示被拦截的方法名称、参数列表和返回结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<span> 1</span>  <span>///</span> <span>&lt;summary&gt;</span>
<span> 2</span> <span>///</span><span> 拦截器 需要实现 IInterceptor接口 Intercept方法
</span><span> 3</span> <span>///</span> <span>&lt;/summary&gt;</span>
<span> 4</span> <span>public</span> <span>class</span><span> CallLogger: IInterceptor
</span><span> 5</span> <span> {
</span><span> 6</span> <span> TextWriter _output;
</span><span> 7</span>
<span> 8</span> <span>public</span><span> CallLogger(TextWriter output)
</span><span> 9</span> <span> {
</span><span>10</span> _output =<span> output;
</span><span>11</span> <span> }
</span><span>12</span>
<span>13</span> <span>///</span> <span>&lt;summary&gt;</span>
<span>14</span> <span>///</span><span> 拦截方法 打印被拦截的方法执行前的名称、参数和方法执行后的 返回结果
</span><span>15</span> <span>///</span> <span>&lt;/summary&gt;</span>
<span>16</span> <span>///</span> <span>&lt;param name="invocation"&gt;</span><span>包含被拦截方法的信息</span><span>&lt;/param&gt;</span>
<span>17</span> <span>public</span> <span>void</span><span> Intercept(IInvocation invocation)
</span><span>18</span> <span> {
</span><span>19</span>
<span>20</span> _output.WriteLine(<span>"</span><span>你正在调用方法 \"{0}\" 参数是 {1}... </span><span>"</span><span>,
</span><span>21</span> <span> invocation.Method.Name,
</span><span>22</span> <span>string</span>.Join(<span>"</span><span>, </span><span>"</span>, invocation.Arguments.Select(a =&gt; (a ?? <span>""</span><span>).ToString()).ToArray()));
</span><span>23</span>
<span>24</span> <span>//</span><span>在被拦截的方法执行完毕后 继续执行</span>
<span>25</span> <span> invocation.Proceed();
</span><span>26</span>
<span>27</span> _output.WriteLine(<span>"</span><span>方法执行完毕,返回结果:{0}</span><span>"</span><span>, invocation.ReturnValue);
</span><span>28</span> <span> }
</span><span>29</span> }

  第二步:注册拦截器到Autofac容器

  拦截器必须注册到Aufofac容器中,可以通过拦截器类型或者命名注入,这两种方式会让使用拦截器的方法有所不同(后面会讲到)。

1
2
3
4
5
6
<span>1</span>             <span>//</span><span> 命名注入</span>
<span>2</span> builder.Register(c =&gt; <span>new</span><span> CallLogger(Console.Out))
</span><span>3</span> .Named&lt;IInterceptor&gt;(<span>"</span><span>log-calls</span><span>"</span><span>);
</span><span>4</span>
<span>5</span> <span>//</span><span> 类型注入</span>
<span>6</span> builder.Register(c =&gt; <span>new</span> CallLogger(Console.Out));

  第三步:启用拦截器

  启用拦截器主要有两个方法:EnableInterfaceInterceptors(),EnableClassInterceptors()。

  EnableInterfaceInterceptors方法会动态创建一个接口代理

  EnableClassInterceptors方法会创建一个目标类的子类代理类,这里需要注意的是只会拦截虚方法,重写方法

  启用拦截器示例代码:

1
2
3
4
<span>//</span><span>启用类代理拦截</span>
builder.RegisterType&lt;Circle&gt;<span>().EnableClassInterceptors();
</span><span>//</span><span>启用接口代理拦截</span>
builder.RegisterType&lt;Circle&gt;().EnableInterfaceInterceptors();

  第四步:指明要拦截的类型

  有两种方法:

    第一种:给类型加上特性Attribute

    

    第二种:在注册类型到容器的时候动态注入拦截器

1
2
<span>1</span>             <span>//</span><span>动态注入拦截器CallLogger</span>
<span>2</span> builder.RegisterType&lt;Circle&gt;().InterceptedBy(<span>typeof</span>(CallLogger)).EnableClassInterceptors();

  第五步:测试效果了

    1.类代理拦截

    

    Circle类代码:

    

     2.接口代理拦截

    

     IShape接口代码:

1
2
3
4
5
6
7
8
<span>1</span> <span>public</span> <span>interface</span><span> IShape
</span><span>2</span> <span> {
</span><span>3</span> <span>///</span> <span>&lt;summary&gt;</span>
<span>4</span> <span>///</span><span> 形状的面积
</span><span>5</span> <span>///</span> <span>&lt;/summary&gt;</span>
<span>6</span> <span>void</span><span> Area();
</span><span>7</span>
<span>8</span> }

    Circle类代码:

1
2
3
4
5
6
7
8
<span>1</span> <span>public</span> <span>class</span><span> Circle:IShape
</span><span>2</span> <span> {
</span><span>3</span> <span>//</span><span>重写父类抽象方法</span>
<span>4</span> <span>public</span> <span>void</span><span> Area()
</span><span>5</span> <span> {
</span><span>6</span> Console.WriteLine(<span>"</span><span>你正在调用圆求面积的方法</span><span>"</span><span>);
</span><span>7</span> <span> }
</span><span>8</span> }

    如果有什么地方写得不对欢迎批评改正,如果有什么疑问,欢迎提问。

.NET(C#)后台发送Get和Post请求的几种方法总结-CJavaPy

Excerpt

.NET (C#) 中,发送 HTTP GET 和 POST 请求可以通过多种方式实现,主要依赖于 .NET Framework 或 .NET Core/5+ 的版本。.NET中提供了多种方法来发送HTTP请求,每种方法都有其优缺点。选择哪种方法取决于具体需求和环境。


1、HttpClient 类 (推荐)

HttpClient 是 .NET 中处理 HTTP 请求的现代方法。它是线程安全的,支持异步操作,并且可以用于多个请求。

适用平台:.NET Framework 4.5+, .NET Standard 1.1+, .NET Core 1.0+

其它平台的移植版本可以通过Nuget来安装。(Nuget使用方法:http://www.cjavapy.com/article/21/)

命名空间:using System.Net.Http;

HttpClient推荐使用单一实例共享使用,发关请求的方法推荐使用异步的方式,代码如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<span>using</span> System;
<span>using</span> System.Collections.Generic;
<span>using</span> System.Linq;
<span>using</span> System.Text;
<span>using</span> System.Threading.Tasks;
<span>using</span> System.Net.Http;

<span>namespace</span> <span>ConsoleApplication</span>
{
<span>class</span> <span>Program</span>
{
<span>private</span> <span>static</span> <span>readonly</span> HttpClient client = <span>new</span> HttpClient();
<span><span>static</span> <span>void</span> <span>Main</span>(<span><span>string</span>[] args</span>)
</span>{

<span>var</span> responseString = <span>await</span> client.GetStringAsync(<span>"http://www.example.com/recepticle.aspx"</span>);

<span>var</span> values = <span>new</span> Dictionary
{
{ <span>"thing1"</span>, <span>"hello"</span> },
{ <span>"thing2"</span>, <span>"world"</span> }
};
<span>var</span> content = <span>new</span> FormUrlEncodedContent(values);
<span>var</span> response = <span>await</span> client.PostAsync(<span>"http://www.example.com/recepticle.aspx"</span>, content);
<span>var</span> responseString = <span>await</span> response.Content.ReadAsStringAsync();
}
}
}<br>

2、使用第三方类库

除了上述方法,还有许多第三方库可以用于发送HTTP请求,例如 RestSharpFlurl。这些库通常提供更高级的功能,例如支持多种身份验证方法和自动重试机制。

1)Flurl.Http(可以通过Nuget来安装)

Flurl.Http 是一个在 .NET 环境下使用的流行的 HTTP 客户端库。它提供了一个简洁的 API 来创建 HTTP 请求,并支持异步操作。Flurl.Http 使得发送 HTTP 请求、接收响应、处理异常和解析数据变得非常简单。

命名空间:using Flurl.Http;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<span>using</span> System;
<span>using</span> System.Collections.Generic;
<span>using</span> System.Linq;
<span>using</span> System.Text;
<span>using</span> System.Threading.Tasks;
<span>using</span> Flurl.Http;

<span>namespace</span> <span>ConsoleApplication</span>
{
<span>class</span> <span>Program</span>
{
<span><span>public</span> <span>static</span> <span>async</span> Task <span>Main</span>(<span><span>string</span>[] args</span>)
</span>{

<span>string</span> getUrl = <span>"https://example.com"</span>;
<span>string</span> postUrl = <span>"https://example.com/post"</span>;


<span>string</span> htmlContent = <span>await</span> GetHtmlAsync(getUrl);
Console.WriteLine(<span>"GET请求结果:"</span>);
Console.WriteLine(htmlContent);


<span>var</span> postData = <span>new</span> { Name = <span>"John"</span>, Age = <span>30</span> };
<span>var</span> postResponse = <span>await</span> PostJsonAsync&lt;<span>object</span>&gt;(postUrl, postData);
Console.WriteLine(<span>"POST请求结果:"</span>);
Console.WriteLine(postResponse);


<span>string</span> htmlContentWithHeaders = <span>await</span> GetHtmlWithHeadersAsync(getUrl);
Console.WriteLine(<span>"带查询参数和头信息的GET请求结果:"</span>);
Console.WriteLine(htmlContentWithHeaders);


<span>string</span> safeHtmlContent = <span>await</span> SafeRequestAsync(getUrl);
Console.WriteLine(<span>"安全GET请求结果:"</span>);
Console.WriteLine(safeHtmlContent);
}
<span><span>public</span> <span>static</span> <span>async</span> Task&lt;<span>string</span>&gt; <span>GetHtmlAsync</span>(<span><span>string</span> url</span>)
</span>{
<span>return</span> <span>await</span> url.GetStringAsync();
}

<span>public</span> <span>static</span> <span>async</span> Task&lt;TResponse&gt; PostJsonAsync&lt;TResponse &gt;(<span>string</span> url, <span>object</span> data)
{
<span>return</span> <span>await</span> url
.PostJsonAsync(data)
.ReceiveJson&lt;TResponse&gt;();
}

<span><span>public</span> <span>static</span> <span>async</span> Task&lt;<span>string</span>&gt; <span>GetHtmlWithHeadersAsync</span>(<span><span>string</span> url</span>)
</span>{
<span>return</span> <span>await</span> url
.SetQueryParams(<span>new</span> { param1 = <span>"value1"</span>, param2 = <span>"value2"</span> })
.WithHeader(<span>"Accept"</span>, <span>"application/json"</span>)
.GetStringAsync();
}

<span><span>public</span> <span>static</span> <span>async</span> Task&lt;<span>string</span>&gt; <span>SafeRequestAsync</span>(<span><span>string</span> url</span>)
</span>{
<span>try</span>
{
<span>return</span> <span>await</span> url.GetStringAsync();
}
<span>catch</span> (FlurlHttpException ex)
{
<span>return</span> <span>"HTTP Error: "</span> + ex.Message;
}
<span>catch</span> (Exception ex)
{
<span>return</span> <span>"General Error: "</span> + ex.Message;
}
}
}
}<br>

2)RestSharp(可以通过Nuget来安装)

RestSharp 是一个用于在 .NET 中发送 HTTP 请求的简单 REST 和 HTTP API 客户端。它可以用于构建和发送各种 HTTP 请求,并处理响应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<span>using</span> System;
<span>using</span> System.Collections.Generic;
<span>using</span> System.Linq;
<span>using</span> System.Text;
<span>using</span> System.Threading.Tasks;
<span>using</span> RestSharp;

<span>namespace</span> <span>ConsoleApplication</span>
{
<span>class</span> <span>Program</span>
{
<span><span>static</span> <span>void</span> <span>Main</span>(<span><span>string</span>[] args</span>)
</span>{

RestClient client = <span>new</span> RestClient(<span>"http://example.com"</span>);
RestRequest req = <span>new</span> RestRequest(<span>"resource/{id}"</span>, Method.GET);
request.AddParameter(<span>"name"</span>, <span>"value"</span>);
request.AddUrlSegment(<span>"id"</span>, <span>"123"</span>);




IRestResponse response = client.Execute(request);


<span>var</span> content = response.Content;


IRestResponse response2 = client.Execute(request);
<span>var</span> name = response2.Data.Name;

client.DownloadData(request).SaveAs(path);

<span>await</span> client.ExecuteAsync(request);

<span>var</span> asyncHandle = client.ExecuteAsync(request, response =&gt; {
Console.WriteLine(response.Data.Name);
});

asyncHandle.Abort();
}

}
}<br>

3、WebRequest 和 WebResponse

较旧的方法,现在通常推荐使用 HttpClient。但在一些旧项目或特定场景下,WebRequestWebResponse 仍然有用。

适用平台:.NET Framework 1.1+, .NET Standard 2.0+, .NET Core 1.0+

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<span>using</span> System;
<span>using</span> System.Collections.Generic;
<span>using</span> System.Linq;
<span>using</span> System.Text;
<span>using</span> System.Threading.Tasks;
<span>using</span> System.Net;
<span>using</span> System.IO;

<span>namespace</span> <span>ConsoleApplication</span>
{
<span>class</span> <span>Program</span>
{
<span><span>static</span> <span>void</span> <span>Main</span>(<span><span>string</span>[] args</span>)
</span>{

<span>var</span> request = (HttpWebRequest)WebRequest.Create(<span>"http://www.example.com/recepticle.aspx"</span>);
<span>var</span> response = (HttpWebResponse)request.GetResponse();
<span>var</span> responseString = <span>new</span> StreamReader(response.GetResponseStream()).ReadToEnd();

<span>var</span> request = (HttpWebRequest)WebRequest.Create(<span>"http://www.example.com/recepticle.aspx"</span>);
<span>var</span> postData = <span>"thing1=hello"</span>;
postData += <span>"&amp;thing2=world"</span>;
<span>var</span> data = Encoding.ASCII.GetBytes(postData);
request.Method = <span>"POST"</span>;
request.ContentType = <span>"application/x-www-form-urlencoded"</span>;
request.ContentLength = data.Length;
<span>using</span> (<span>var</span> stream = request.GetRequestStream())
{
stream.Write(data, <span>0</span>, data.Length);
}
<span>var</span> response = (HttpWebResponse)request.GetResponse();
<span>var</span> responseString = <span>new</span> StreamReader(response.GetResponseStream()).ReadToEnd();
}

}
}<br>

4、通过WebClient的方式发送请求

C#中,WebClient类提供了一个简单的方法来发送和接收数据到和从一个URI(如一个网页或Web服务)上。

适用平台:.NET Framework 1.1+, .NET Standard 2.0+, .NET Core 2.0+

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<span>using</span> System;
<span>using</span> System.Collections.Generic;
<span>using</span> System.Linq;
<span>using</span> System.Text;
<span>using</span> System.Threading.Tasks;
<span>using</span> System.Net;
<span>using</span> System.Collections.Specialized;

<span>namespace</span> <span>ConsoleApplication</span>
{
<span>class</span> <span>Program</span>
{
<span><span>static</span> <span>void</span> <span>Main</span>(<span><span>string</span>[] args</span>)
</span>{

<span>string</span> url = <span>string</span>.Format(<span>"http://localhost:28450/api/values?p1=a&amp;p2=b"</span>);
<span>using</span> (<span>var</span> wc = <span>new</span> WebClient())
{
Encoding enc = Encoding.GetEncoding(<span>"UTF-8"</span>);
Byte[] pageData = wc.DownloadData(url);
<span>string</span> re = enc.GetString(pageData);
}

<span>using</span> (<span>var</span> client = <span>new</span> WebClient())
{
<span>var</span> values = <span>new</span> NameValueCollection();
values[<span>"thing1"</span>] = <span>"hello"</span>;
values[<span>"thing2"</span>] = <span>"world"</span>;
<span>var</span> response = client.UploadValues(<span>"http://www.example.com/recepticle.aspx"</span>, values);
<span>var</span> responseString = Encoding.Default.GetString(response);
}
}

}
}<br>

.NET中AOP的几种实现方案 - 青羽 - 博客园

Excerpt

通过代码的方式演示几种aop方案在.net中的实现。包括静态代理、.Net Remoting/RealProxy、EntLib\PIAB和Castle\DynamicProxy。


AOP在.NET中的应用,个人也属于学习阶段,欢迎大家拍砖!

本文的例子模拟用户注册的场景,主要通过代码演示几种方案的实现方式。

静态代理

通过代理模式实现静态代理,大家一看代码基本就明白了。

用户注册接口和实现

复制代码

    public interface IUserProcessor
    {        void RegUser(User user);
    }    public class UserProcessor : IUserProcessor
    {        public  void RegUser(User user)
        {
            Console.WriteLine(“用户已注册。Name:{0},PassWord:{1}”, user.Name, user.PassWord);
        }
    }

复制代码

通过静态编写代码的方式,装饰上面的用户注册

复制代码

public class UserProcessorDecorator:IUserProcessor
    {        public IUserProcessor UserProcessor { get; set; }        public UserProcessorDecorator(IUserProcessor userprocessor)
        {
            UserProcessor = userprocessor;
        }        public  void RegUser(User user)
        {
            PreProceed(user);
            UserProcessor.RegUser(user);
            PostProceed(user);
        }        public void PreProceed(User user)
        {
            Console.WriteLine(“方法执行前”);
        }public void PostProceed(User user)
        {
            Console.WriteLine(“方法执行后”);
        }
    }

复制代码

客户端调用

复制代码

    public class Client
    {        public static void Run()
        {            try            {
                User user = new User() { Name = “lee”, PassWord = “123123123123” };
                IUserProcessor userprocessor = new UserProcessorDecorator(new UserProcessor());
                userprocessor.RegUser(user);
            }            catch (Exception ex)
            {                throw ex;
            }
        }
    }

复制代码

输出

方法执行前
用户已注册。Name:lee,PassWord:123123123123
方法执行后

动态代理

1、使用.Net Remoting/RealProxy

采用TransparentProxy和RealProxy实现对象的代理,实现思路如下:Client -TransparentProxy - RealProxy - Target Object

下面实现自定义的TransparentProxy和RealProxy

复制代码

using System.Runtime.Remoting.Proxies;using System.Runtime.Remoting.Messaging;    //RealProxy
    public class MyRealProxy<T>:RealProxy
    {        private T _target;        public MyRealProxy(T target) : base(typeof(T))
        {            this._target = target;
        }       public override IMessage Invoke(IMessage msg)
       {
            PreProceede(msg);
            IMethodCallMessage callMessage = (IMethodCallMessage)msg;            object returnValue = callMessage.MethodBase.Invoke(this._target, callMessage.Args);
            PostProceede(msg);            return new ReturnMessage(returnValue, new object[0], 0, null, callMessage);
        }       public void PreProceede(IMessage msg)
       {
           Console.WriteLine(“方法执行前”);
       }       public void PostProceede(IMessage msg)
       {
           Console.WriteLine(“方法执行后”);
       }
    }   //TransparentProxy
   public static class TransparentProxy
   {        public static T Create<T>()
        {
           T instance = Activator.CreateInstance<T>();
           MyRealProxy<T> realProxy = new MyRealProxy<T>(instance);
           T transparentProxy = (T)realProxy.GetTransparentProxy();           return transparentProxy;
        }
   }

复制代码

用户注册接口和实现

复制代码

  public interface IUserProcessor
    {        void RegUser(User user);
    }public class UserProcessor : MarshalByRefObject, IUserProcessor
    {        public void RegUser(User user)
        {
            Console.WriteLine(“用户已注册。”);
        }
    }

复制代码

客户端调用

复制代码

 public class Client
    {        public static void Run()
        {            try            {
                User user = new User() { Name = “lee”, PassWord = “123123123123” };
                UserProcessor userprocessor = TransparentProxy.Create<UserProcessor>();
                userprocessor.RegUser(user);
            }            catch (Exception ex)
            {                throw ex;
            }
        }
    }

复制代码

输出

方法执行前
用户已注册。Name:lee,PassWord:123123123123
方法执行后
2、使用EntLib\PIAB
自定义CallHandler,这里定义两个CallHandler分别用于参数检查和日志记录。

复制代码

using Microsoft.Practices.Unity.InterceptionExtension;public class UserHandler:ICallHandler
    {        public int Order { get; set; }        public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
        {
            User user = input.Inputs[0] as User;            if (user.PassWord.Length < 10)
            {                return input.CreateExceptionMethodReturn(new UserException(“密码长度不能小于10位”));
            }
            Console.WriteLine(“参数检测无误”);            return getNext()(input, getNext);
        }
    }public class LogHandler:ICallHandler
    {        public int Order { get; set; }        public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
        {
            User user = input.Inputs[0] as User;
            Log log = new Log() { Message = string.Format(“RegUser:Username:{0},Password:{1}”, user.Name, user.PassWord), Ctime = DateTime.Now };
            Console.WriteLine(“日志已记录,Message:{0},Ctime:{1}”,log.Message,log.Ctime);
            var messagereturn  = getNext()(input, getNext);             return messagereturn;
        }
    }

复制代码

定义对应的HandlerAttribute

复制代码

using Microsoft.Practices.Unity.InterceptionExtension;using Microsoft.Practices.Unity;public class UserHandlerAttribute : HandlerAttribute
    {        public override ICallHandler CreateHandler(IUnityContainer container)
        {
            ICallHandler handler = new UserHandler(){Order=this.Order};            return handler;
        }
    }public  class LogHandlerAttribute:HandlerAttribute
    {        public int Order { get; set; }        public override ICallHandler CreateHandler(IUnityContainer container)
        {            return new LogHandler() { Order = this.Order };
        }
    }

复制代码

用户注册接口和实现,这里通过为接口添加attribute的方式实现。order值表示执行顺序,值小的先执行。

复制代码

    [LogHandlerAttribute(Order=2)]
    [UserHandlerAttribute(Order=1)]    public interface IUserProcessor
    {         void RegUser(User user);
    }public class UserProcessor : MarshalByRefObject,IUserProcessor
    {        public  void RegUser(User user)
        {
            Console.WriteLine(“用户已注册。”);
        }
    }

复制代码

客户端调用

复制代码

using Microsoft.Practices.EnterpriseLibrary.PolicyInjection; public class Client
    {        public static void Run()
        {            try            {
                User user = new User() { Name = “lee”, PassWord = “123123123123” };
                UserProcessor userprocessor = PolicyInjection.Create<UserProcessor>();
                userprocessor.RegUser(user);
            }            catch(Exception ex)
            {                throw ex;
            }
        }
    }

复制代码

输出:

参数检测无误
日志已记录,Message:RegUser:Username:lee,Password:123123123123,Ctime:2010-12-22
6:14:59
用户已注册。
3、使用Castle\DynamicProxy
自定义Interceptor

复制代码

 public class MyInterceptor : IInterceptor
    {        public void Intercept(IInvocation invocation)
        {
            PreProceed(invocation);
            invocation.Proceed();
            PostProceed(invocation);
        }        public void PreProceed(IInvocation invocation)
        {
            Console.WriteLine(“方法执行前”);
        }public void PostProceed(IInvocation invocation)
        {
            Console.WriteLine(“方法执行后”);
        }
    }

复制代码

用户注册接口和实现

复制代码

 public interface IUserProcessor
    {        void RegUser(User user);
    }public class UserProcessor : IUserProcessor
    {        public virtual void RegUser(User user)
        {
            Console.WriteLine(“用户已注册。Name:{0},PassWord:{1}”, user.Name, user.PassWord);
        }
    }

复制代码

客户端调用

复制代码

 public class Client
    {        public static void Run()
        {            try            {
                ProxyGenerator generator = new ProxyGenerator();
                MyInterceptor interceptor = new MyInterceptor();
                UserProcessor userprocessor = generator.CreateClassProxy<UserProcessor>(interceptor);
                User user= new User() { Name = “lee”, PassWord = “123123123123” };
                userprocessor.RegUser(user);
            }            catch (Exception ex)
            {                throw ex;
            }
        }
    }

复制代码

输出

方法执行前
用户已注册。Name:lee,PassWord:123123123123
方法执行后

关于上面各方案的详细介绍园子里都有很好的文章,我就不班门弄斧了。

另,公司最近在招聘,做个广告,欢迎有意者投递简历。

http://job.cnblogs.com/offer/10501/

NET实现的DDD、CQRS与微服务架构 - HackerVirus - 博客园

Excerpt

WeText项目:一个基于.NET实现的DDD、CQRS与微服务架构的演示案例 最近出于工作需要,了解了一下微服务架构(Microservice Architecture,MSA)。我经过两周业余时间的努力,凭着自己对微服务架构的理解,从无到有,基于.NET打造了一个演示微服务架构的应用程序案例,并


最近出于工作需要,了解了一下微服务架构(Microservice Architecture,MSA)。我经过两周业余时间的努力,凭着自己对微服务架构的理解,从无到有,基于.NET打造了一个演示微服务架构的应用程序案例,并结合领域驱动设计(DDD)以及命令查询职责分离(CQRS)体系结构模式,对事件驱动的微服务系统架构进行了一些实战性的探索。现将自己的思考和收获整理成文,分享给大家。

微服务架构

在介绍源代码之前,我还是想谈谈微服务架构,虽然网上有很多有关微服务架构的讨论,但我觉得在此再多说一些还是有必要的。大师级人物Martin Fowler在他谈论微服务的个人主页上提到,微服务并没有一个非常明确的定义。事实上有很多种分布式系统的实现都可以被看成(或者说勉强看成)是面向微服务架构的。就我个人而言,我觉得微服务架构应该满足以下几个特征:

  • 整个系统被分为多个业务功能相对独立的一体化架构Monolithic Architecture,或称单一化架构)的应用程序(也就是所谓的“微服务”),每个微服务通常遵循标准的分层架构风格或者基于事件驱动的架构风格,能够对自己相关的领域逻辑进行处理,使用本地数据库进行数据存储,并向上层提供相对独立的API接口或者用户界面。每个微服务还可以使用诸如缓存、日志等基础结构层设施,但如果是与其它的微服务公用这些设施,则该基础结构层设施需要满足下面的第三条特征
  • 各个微服务之间可以使用以下方式进行通信(参见:http://howtocookmicroservices.com/communication/
    • 同步方式:最为常见的是基于RESTful风格的API,也可以是跨平台、跨语言的Apache Thrift IDL
    • 异步方式:使用轻量级的消息通信机制,比如RabbitMQ、Redis等
  • 整个系统是“云友好”(cloud-friendly)的。所谓的“云友好”,是指:
    • 针对每个微服务,都应该避免单点失败的可能。例如针对一个系统中某个微服务A,需要有至少两个(或以上)的运行实例,并由API网关(API Gateway)或者负载均衡器(Load Balancer)根据一定的规则(比如各个A的运行实例的健康程度等)将来自客户端的服务请求分配到任意一个A的运行实例上完成处理
    • 针对每个微服务,管理员可以根据一些特定的实时技术指标对这些应用程序的部署进行调整。例如,购物网站的查询服务负载明显要高于订单管理服务,那么管理员可以根据实际情况,增加查询服务的部署量(比如部署3个查询服务的实例),同时减少订单管理服务的部署量。与整个系统单一采用一体化架构相比,这样做的好处是显而易见的,它能够充分利用云端服务器资源,使得每个微服务都能够运行在合理的资源配置状态下,减少资源浪费
    • 公有基础结构层服务设施也应该满足避免单点失败的条件,例如数据库服务需要配置Replication/Clustering,消息队列也需要使用类似的fault tolerance策略
    • 基于“云友好”的需求,衍生了一大堆的部署和运维问题,比如微服务本身的配置模式(每台机器配置多个实例?还是每台机器配置一个实例?还是每台虚拟机配置一个实例?又或者是将实例配置到类似Docker这样的容器中)?消息队列如何配置才能支持同一个微服务的多个实例不会重复处理相同的消息?基于RESTful的通信如何让客户端找到动态改变的API地址?等等。我想,这些问题并没有一个特定的答案,还是需要根据实际情况来进行判定

相比传统的一体化架构系统,微服务架构系统有着以下一些优势:

  • 每个微服务都相对较小,这样更加便于开发和调试
  • 每个微服务都相对独立,这样不仅可以使开发人员仅关注在某个业务处理部分,而且还可以针对每个微服务自己的特征,采用不同的技术实现(比如部分微服务使用C#实现,部分使用Java或者Python等)
  • 这种独立性使得微服务在容错隔离方面也有很好的表现:比如某个微服务出现了crash等问题,不会导致整个系统不可用,这符合BASE(Basically Available, Soft-state, Eventually consistency)理念
  • 由于相对独立,微服务架构的设计能够更方便地部署到云环境中
  • 微服务的独立性还为敏捷开发提供了很好的支持。比如每个服务都可以单独开发单独部署,同时项目团队还能根据成员本身的技术专长来平衡开发和测试资源

当然,它也有一些不足:

  • 开发人员需要应对由分布式架构带来的复杂性。比如如果微服务间采用异步的消息通信机制进行通信,那么就需要遵循由这种消息机制所引入的开发模式(创建消息处理器Message Handler,转发消息等)。此外,这种架构为测试工作也带来了很多不方便的因素,例如当某些测试用例(Test Cases)需要涵盖多个微服务的业务时,就需要关注弱一致性分布式事务的执行结果,而这往往是比较复杂的。更进一步,这种测试工作还需要多个团队的协调才能顺利进行,当各个团队分布在全球各个国家各个地区时,协调工作更是变得复杂甚至难以进行
  • 在生产环境中部署、安装和管理基于微服务架构的系统也不是件容易的事情。这需要客户方有着较强的专业技术背景和解决问题的能力。当然,一种更好的方式是以SaaS的方式直接将服务提供给消费者
  • 较多的资源消耗。出于隔离和容错需要,微服务有可能被部署为N个实例,每个实例运行于独立的虚拟系统中。假设部署策略不当造成系统资源存在一定的浪费,那么这种浪费也有可能被扩大N倍

有关微服务架构的内容暂时就写这么多吧,微服务架构现在比较火爆,大家也可以直接上网查阅相关资料,英语比较好的朋友建议直接上英文网站去搜索学习,有很多精华文章和精彩讨论。架构本身就是仁者见仁智者见智,不同的人有不同的理解,产生了不同的观点,有些观点可能在有些场景下更为合适,但换个场景又体现了它的弱势。但不管怎样,我想说的是,无论选择什么架构,它总有优缺点,架构设计的难处就在于如何选择最为合适的模式、方法、技术来完成一整套系统开发的解决方案。更多情况下,整个应用系统更有可能是融合了多种技术多种架构风格的“生态圈”。对于你现在正在开发的项目,或许使用经典的三层架构最为合适。

WeText项目

有理论还需要实践。为此,我花了两周的业余时间,使用Visual Studio 2015开发了一个案例项目:WeText。这个案例项目的业务还是很简单的:用户可以注册、登录,登录后可以修改个人信息,然后可以创建一些自己的Text(就是含有标题和文本内容的小笔记),还可以发送加好友申请给其他用户,等对方接受邀请后,可以将自己的Text分享给对方。到我写本文为止,Text分享部分还没有完成,但其它业务部分基本已经走通,可能还有不少Bug。

看到这里,你肯定会要吐槽了,这么简单的系统还需要花两周,搞出这么大动静,还有这么多Bug,居然还没搞完!是的,目前还不太完善,为什么?因为架构复杂,我是边思考边设计边Coding,或许使用CQRS的微服务架构并不适合这样的应用系统,甚至DDD也未必有用武之地。在这个项目上采用这么个架构风格,老实说,我只是为了实践一下。到目前为止,这个项目还有以下不足之处,还请各位读者忍耐一下。当然,它是开源的(Apache 2.0 License),你觉得没有尽兴的地方也欢迎参与讨论和贡献,提交Pull Request给我就行了。

  • CQRS的查询部分采用了关系型数据库,数据库访问层面没有使用ORM,仅实现了Table Data Gateway模式,但Table Data Gateway的实现是单表型结构,跨表查询无法完成JOIN操作:有兴趣的朋友可以基于已有的WeText项目自己实现另一套基于ORM的查询机制
  • 虽然Web程序主页上宣称采用了Event Sourcing,但实际上我没有在Event Store中记录任何事件,只是将聚合的最终状态保存在Event Store中(出于时间考虑,否则再搞一个月也不一定完得成,时间精力耗不起啊)。CQRS没有Event Sourcing,Oh my god!不过别惊讶,CQRS不一定非要采用Event Sourcing:有兴趣的朋友可以基于已有的WeText项目自己实现Event Sourcing的功能,但别忘了将Snapshot也一并搞定,这个非常重要!你还可以在WeText上使用成熟的Event Store框架来完成这部分功能。有结论了别忘了分享出来
  • CQRS的命令部分由RESTful API封装。由于命令执行是异步的(仅保证最终一致性),而RESTful API是同步的,导致RESTful API无法返回命令执行的最终结果。我在考虑是否还需要引入诸如Akka这样的基于Actor模型的方案来解决这样的问题,但也不一定有效。还在寻求解决方案。有兴趣的朋友可以继续深入地考虑这个问题
  • 异常处理部分相对较弱:这部分我会继续加强
  • 前端界面(WeText.Web项目)相对较丑,也有一些缺陷,就是简单的使用ASP.NET MVC 5结合Bootstrap做的,没有使用TypeScript+AngularJS、React甚至是jQuery搞一些高大上的用户体验,基本满足对后端业务的支撑。有兴趣的朋友可以扔掉WeText.Web项目,仅使用WeText提供的服务自己开发自己的前端界面
  • 暂时还没有完全验证在云端的部署是否可行,理论上可行,但没有完全验证,等有结论了我再另外发文介绍吧

整体架构

首先,让我们从整体架构角度来了解一下WeText项目的整个结构,以及它所包含的各个组件。

image

上图中,蓝色部分表示与领域相关的概念,诸如聚合、规约、事件、Saga、仓储等;黄色部分表示微服务,目前有Accounts、Texting以及Social三个微服务;灰色部分表示基础结构层设施,包括基于Owin的Web API宿主程序、消息队列、Event Store以及数据库等;浅粉红色色块表示一个服务宿主进程(Service Host)。

  • 客户端程序通过RESTful API(Web API)将命令请求发送到服务端
  • 服务端通过API Gateway或者Load Balancer将请求转发到相应的微服务实例(API Gateway和Load Balancer没有体现在上图中,那是另一件事情,今后我会讨论)
  • Web API Controller将请求转换为CQRS的Command,派发到Command Queue
  • Command Handler获得Command消息,通过Repository访问Domain(这个过程会牵涉到Snapshot),执行命令操作
  • Repository在保存聚合时,会将操作所产生的事件存储到Event Store(这个过程会牵涉到Snapshot),同时将领域事件派发到消息队列Event Queue
  • Event Handler在获取到消息后,执行消息相关操作,在Event Handler中会触发Saga状态的转换,Saga状态变化后,会产生状态变化领域事件,这个领域事件的Event Handler又会触发另一个Command的发生(理论上应该是在Saga中直接触发Command,但Saga本身也应该是聚合根,因此由Saga直接操作Command派发明显不合理,这部分内容之后再讨论)
  • Event Handler会根据需要同时更新Query Database(也就是上图中normalize的步骤)
  • 客户端的查询请求会直接经由RESTful API(Web API),通过Table Data Gateway访问Query Database直接完成

对于Service Host,在上图中它同时为三个服务实例提供了宿主环境。事实上,WeText的设计允许Service Host仅宿主其中的某个或者某几个实例,而多个Service Host又可以被部署到多个不同的物理机器上,例如:

image

于是,在整个环境中,我们有一个Accounts服务实例、两个Texting服务实例和两个Social服务实例。至少在单点失败和服务器资源平衡方面提供了解决方案,当然也带来了不少问题。比如:

  • 如何配置API Gateway的路由,使得客户端请求能够根据指定的策略派发到相应的微服务实例上完成处理?
  • 对于具有多个实例的微服务,基于Pub/Sub的消息订阅机制如何避免事件或者命令的重复处理?

这些问题我会在后续文章中讨论。

另外,你会认为基础结构层设施存在单点失败可能,比如RabbitMQ或者数据库。其实这些成熟的产品都有自己的解决方案,比如做数据库集群。或者干脆直接使用AWS或者Azure提供的PaaS服务(消息队列、存储等)。因此,解决这个问题并不困难。

开始

为了能够更好地了解WeText整个项目的架构和所使用的技术,建议提前对以下内容做些了解:

接下来,重要的事情,算了,就说一遍吧,请使用git将项目代码克隆到本地:

然后直接使用Visual Studio 2015打开WeText.sln文件即可。打开代码后,先别急着运行,让我们先了解一下项目结构。

  • **Services**目录:包含了三个微服务的项目:Accouts(用户账户微服务)、Social(社交微服务)以及Texting(小笔记微服务)
  • **WeText.Common**项目:包含了整个解决方案的基础库
  • **WeText.Domain**项目:领域模型与命令、事件定义
  • **WeText.DomainRepositories**项目:领域仓储的具体实现(MongoDB实现)
  • **WeText.Messaging.RabbitMq**项目:基于RabbitMQ的消息系统实现
  • **WeText.Querying.MySqlClient**项目:基于MySQL的Table Data Gateway实现,用于提供对MySQL数据库的CRU操作(暂不支持Delete)
  • **WeText.Querying.PostgreSQL**项目:基于PostgreSQL的Table Data Gateway实现,用于提供对PostgreSQL数据库的CRU操作(暂不支持Delete)
  • **WeText.Service**项目:微服务的宿主程序,启动后是一个控制台服务端程序(运行时先启动此项目)
  • **WeText.Tests**项目:基于NUnit的单元测试项目,请直接忽略
  • **WeText.Web**项目:前端用户界面项目,在WeText.Service项目启动后,再启动本项目
  • 此外,在we-text根目录下,还有一个scripts的子目录,里面包含了针对MySQL和PostgreSQL数据库的初始化脚本(在写此文时,PostgreSQL脚本未加入,之后会加),在后面的安装步骤中会用到

外部依赖项(External Dependencies)

首先,WeText仅依赖于一些基础结构层设施所需的相关库,包括:

  • MySql.Data
  • RabbitMQ.Client
  • MongoDB
  • Npgsql
  • Autofac相关
  • Owin相关
  • Newtonsoft Json
  • log4net
  • 与ASP.NET MVC相关的库

除此之外,没有使用任何应用层的开发框架和代码库,所有的代码都是原创并且包含在整个WeText的解决方案中。

其次,服务端基础结构层完全选用诸如Owin、MySQL、RabbitMQ、PostgreSQL等这些能够跨平台的项目和产品,如此一来整个WeText服务端能够完全部署在Linux环境中(其实这也是我想实践的一个部分,验证基于Mono的.NET服务器程序在Linux系统中是否有出色的表现)。没有使用SQL Server、Entity Framework这些目前更适合运行于Windows平台的产品。

安装与运行

首先,为了方便起见,强烈建议将所有的服务和程序安装在同一台机器上。请按以下步骤准备系统环境:

  1. 下载源代码(参考上面的git命令)
  2. 下载并安装RabbitMQ,安装过程使用默认配置(包括服务端口等等),有关RabbitMQ的安装,请参见:https://www.rabbitmq.com/download.html
  3. 下载并安装MongoDB,安装过程使用默认配置(包括服务端口等等),有关MongoDB的安装,请参见:https://docs.mongodb.org/manual/installation/,如安装后需要更改WeText的MongoDB配置,请移步到WeText.DomainRepositories项目下的MongoSetting.cs文件(写本文时还是hard code在代码里,今后会移到App.config中)
  4. 下载并安装MySQL Community Edition(包含服务器和客户端),安装过程使用默认配置,root密码请采用P@ssw0rd。有关MySQL的安装,请参见:http://dev.mysql.com/doc/refman/5.7/en/installing.html,如果安装后需要更改WeText的MySQL配置,请直接修改WeText.Service项目的App.config文件
  5. 如果你打算使用PostgreSQL作为查询数据库,那么你只需要安装PostgreSQL即可,不需要安装MySQL。安装过程也请使用默认配置
  6. 使用we-text项目文件夹下scripts目录下的SQL脚本初始化对应的数据库,目前PostgreSQL的脚本还没有加进来,之后会添加

环境准备好之后,就可以试着启动项目了。

在Windows系统中启动并调试项目

  1. 使用Visual Studio 2015打开WeText.sln文件
  2. 启动WeText.Service项目,应该能看到以下画面: 
    image 
  3. 启动WeText.Web项目,应该能看到以下画面: 
    image 
  4. 尝试点击Register注册自己的账户并登录

在Linux中编译并启动服务器程序

注意:我目前还没有来得及测试使用WeText.Web站点访问部署在Linux上的服务器,仅试图在Linux环境中编译和启动服务器程序。Web站点程序(WeText.Web)本身暂不打算运行于Linux环境,以后可以尝试。

  1. 安装Mono,请根据该页面完成Mono的安装:http://www.mono-project.com/docs/compiling-mono/linux/。建议直接从Release Package安装,可以到http://download.mono-project.com/sources/mono/下载最新版本的Mono。WeText需要.NET Framework 4.6.1和C# 6.0的支持
  2. 同样需要根据上面的步骤准备系统环境,包括RabbitMQ、MongoDB以及查询数据库的安装和初始化
  3. 使用上面的git命令下载源代码
  4. 由于目前nuget在Mono下的支持还是有些问题,有些package无法下载,建议可以先在Windows下编译WeText,然后将下载的packages目录上传到Linux中we-text\src目录下
  5. 进入we-text\src目录,使用该命令完成编译:xbuild /p:TargetFrameworkVersion=v4.6.1 /p:Configuration=ServerDebug WeText.sln。编译可能会出现部分警告,暂时请直接忽略
  6. 进入we-text\bin目录,执行./WeText.Service.exe命令,应该能看到以下画面: 
    image 
  7. 如需尝试从WeText.Web站点访问Linux上的服务,暂时请在WeText.Web项目中查找http://localhost:9023/字符串,并将localhost替换成Linux主机的服务URL即可。

总结

本文首先简要介绍了微服务架构,并从整体架构、代码库的使用、环境准备和编译部署等方面介绍了WeText这个基于.NET实现的DDD、CQRS和微服务架构的演示案例。对微服务感兴趣的朋友欢迎试用本案例源代码,并欢迎参与更深入的探讨。WeText目前还是不太成熟,我也会逐步去完善这个案例,同时也会在此过程中分享自己的心得体会,欢迎大家关注。