前端技术
HTML
CSS
Javascript
前端框架和UI库
VUE
ReactJS
AngularJS
JQuery
NodeJS
JSON
Element-UI
Bootstrap
Material UI
服务端和客户端
Java
Python
PHP
Golang
Scala
Kotlin
Groovy
Ruby
Lua
.net
c#
c++
后端WEB和工程框架
SpringBoot
SpringCloud
Struts2
MyBatis
Hibernate
Tornado
Beego
Go-Spring
Go Gin
Go Iris
Dubbo
HessianRPC
Maven
Gradle
数据库
MySQL
Oracle
Mongo
中间件与web容器
Redis
MemCache
Etcd
Cassandra
Kafka
RabbitMQ
RocketMQ
ActiveMQ
Nacos
Consul
Tomcat
Nginx
Netty
大数据技术
Hive
Impala
ClickHouse
DorisDB
Greenplum
PostgreSQL
HBase
Kylin
Hadoop
Apache Pig
ZooKeeper
SeaTunnel
Sqoop
Datax
Flink
Spark
Mahout
数据搜索与日志
ElasticSearch
Apache Lucene
Apache Solr
Kibana
Logstash
数据可视化与OLAP
Apache Atlas
Superset
Saiku
Tesseract
系统与容器
Linux
Shell
Docker
Kubernetes
[跨平台OPC UA协议配置教程]的搜索结果
这里是文章列表。热门标签的颜色随机变换,标签颜色没有特殊含义。
点击某个标签可搜索标签相关的文章。
点击某个标签可搜索标签相关的文章。
转载文章
...站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 PLC通讯实现-C访问OpcUa实现读写PLC(十) 背景 概念 特点 依赖 配置OpcUA Server 关键代码 代码下载 背景 由于工厂设备种类多、分阶段建设,工控程序开发通常面临对接多种PLC厂商设备和不同系列与型号。因此出现了一种专门与不同PLC通讯的软件协议-OPC(OLE for Process Control),而各厂家在OPC基础上进行了不同程度的扩展,为了应对标准化和跨平台的趋势,和了更好的推广OPC,OPC基金会近些年在之前OPC成功应用的基础上推出了一个新的OPC标准-OPC UA。处于通讯效率上的考虑,很多厂家生产了OPCUA设备模块,内置处理器,性价比不错。不过这不是本文关注的重点。 概念 OPC UA(OPC Unified Architecture)是指OPC统一体系架构,是一种基于服务的、跨越平台的解决方案。 特点 扩展了OPC的应用平台。传统的基于COM/DCOM 的OPC技术只能基于Windows操作系统,OPC UA支持拓展到Linux和Unix平台。这使得基于OPC UA的标准产品可以更好地实现工厂级的数据采集和管理; 不再基于DCOM通讯,不需要进行DCOM安全设置; OPC UA定义了统一数据和服务模型,使数据组织更为灵活,可以实现报警与事件、数据存取、历史数据存取、控制命令、复杂数据的交互通信; OPC UA比OPC DA更安全。OPC UA传递的数据是可以加密的,并对通信连接和数据本身都可以实现安全控制。新的安全模型保证了数据从原始设备到MES,ERP系统,从本地到远程的各级自动化和信息化系统的可靠传递; OPC UA可以穿越防火墙,实现Internet 通讯。 依赖 我们通常不会从头写,可以基于OpcUa.core.dll库和OpcUa.Client.dll库,而且附上这2个库的源代码。 配置OpcUA Server 您可以安装任何一款支持OPCUA的服务端软件进行以下配置(此为示例配置,您可根据你的实际情况进行配置) 1、OpcUa Server Url:opc.tcp://192.168.100.1:4840。 2、OpcUa EndPoint:[UaServer@cMT-EAB9] [None] [None] [opc.tcp://192.168.100.1:4840/G01] 3、PLC Device Name:Siemens S7-1200/S7-1500 4、Account:user1 5、Password:自己设置 6、在PLC中开了2个数据块,分别为DB4长度110个字、DB5长度122个字。 7、对应第4块创建标签,第一个名称为DB4.0-99,地址为DB4DBW0.100,数据类型为Short,长度100,即定义长度最长为100的Short数组。第二个名称为DB4.100-109,地址为DB4DBW100.10,数据类型为Short,方便快速读取。 5、对应第5块创建3个标签,第一个名称为DB5.0-99,地址为DB5DBW0.100,数据类型为Short,第二个名称为DB5.100-121, 地址为DB5DBW100.22,数据类型为Short,即定义长度最长为100的Short数组。方便快速读取。第三个标签名称为DB5DBW64,地址为DB5DBW64,数据类型为Short。 具体如下图: 关键代码 using System;using System.Collections.Generic;using System.Linq;using Opc.Ua.Helper;using Mesnac.Equips;namespace Mesnac.Equip.OPC.OpcUa.OPCUA{public class Equip : BaseEquip{region 字段定义private bool _isOpen = false; //是否已打开设备private bool _isClosing = false; //是否正在关闭设备private OPCUAClass myOpcHelper; //OPCUA设备访问辅助对象private Dictionary<string, string> dicTags = null; //保存标签集合private Dictionary<string, object> readResult = null; //设备标签数据缓存private int stepLen = 250; //标签变量的步长设置private string groupNamePrefix = "DB"; //数据块号前缀private string childTagFlag = "~"; //子元素标签标志符private System.Threading.Thread innerReadThread = null; //内部读取线程对象private int innerReadRate = 1000; //内部读取频率endregionregion 属性定义/// <summary>/// OPCUA Server Url/// </summary>public string OpcUaServerUrl{get{//return (this.Main.ConnType as Mesnac.Equips.Connection.OPCUA.ConnType).OpcUaServerUrl;return "opc.tcp://192.168.1.102:4840";//return "opc.tcp://192.168.100.1:4840";//return "opc.tcp://192.168.100.2:4840";} }/// <summary>/// 要连接的OPCUA服务器上的服务名/// </summary>public string OpcUaServiceName{get{//return (this.Main.ConnType as Mesnac.Equips.Connection.OPCUA.ConnType).OpcUaServiceName;return "[UaServer@cMT-9F1F] [None] [None] [opc.tcp://192.168.1.102:4840/G01]";//return "[UaServer@cMT-EAB9] [None] [None] [opc.tcp://192.168.100.1:4840/G01]";//return "[UaServer@cMT-EA5B] [None] [None] [opc.tcp://192.168.100.2:4840/G02]";//return "[UaServer@cMT-EA5B] [None] [None] [opc.tcp://192.168.100.2:4840/G01]";} }/// <summary>/// 要连接的OPCUA服务器上指定服务名下的PLC的名称/// </summary>public string PLCName{get{//return (this.Main.ConnType as Mesnac.Equips.Connection.OPCUA.ConnType).PLCName;//return "Feeding";return "Siemens_192.168.2.1";//return "Rockwell_192.168.1.10";} }/// <summary>/// OPCUA服务器的访问账户/// </summary>public string Account{get{//return (this.Main.ConnType as Mesnac.Equips.Connection.OPCUA.ConnType).Account;return "user1";} }/// <summary>/// OPCUA服务器的访问密码/// </summary>public string Password{get{//return (this.Main.ConnType as Mesnac.Equips.Connection.OPCUA.ConnType).Password;return "1";} }endregionregion BaseEquip成员实现/// <summary>/// 打开连接设备/// </summary>/// <returns>成功返回true,失败返回false</returns>public override bool Open(){lock (this){this._isClosing = false;if (this._isOpen == true && this.myOpcHelper != null){return true;}this.State = false;this.myOpcHelper = new OPCUAClass();this.dicTags = this.myOpcHelper.ConnectOPCUA(this.OpcUaServerUrl, this.Account, this.Password, this.OpcUaServiceName, this.PLCName); //连接OPCServerif (this.dicTags == null || this.dicTags.Count == 0){this.myOpcHelper = null;Console.WriteLine("OPC连接失败!");this.State = false;return false;}else{this.State = true;this._isOpen = true;region 初始化读取结果this.readResult = new Dictionary<string, object>();foreach (Equips.BaseInfo.Group group in this.Group.Values){if (!group.IsAutoRead){continue;}int groupMinStart = group.Start;int groupMaxEnd = group.Start + group.Len;int groupMaxLen = group.Len;foreach (Equips.BaseInfo.Group g in this.Group.Values){if (!g.IsAutoRead){continue;}if (g.Block == group.Block){if (g.Start < group.Start){groupMinStart = g.Start;}if (g.Start + g.Len > groupMaxEnd){groupMaxEnd = g.Start + g.Len;} }}groupMaxLen = groupMaxEnd - groupMinStart;int tagCount = groupMaxLen % this.stepLen == 0 ? groupMaxLen / this.stepLen : groupMaxLen / this.stepLen + 1;int currLen = 0;for (int i = 0; i < tagCount; i++){string tagName = String.Empty;if (tagCount == 1){tagName = String.Format("{0}-{1}", groupMinStart, groupMinStart + groupMaxLen - 1);currLen = groupMaxLen;}else if (i == tagCount - 1){tagName = String.Format("{0}-{1}", groupMinStart + (i this.stepLen), groupMinStart + (i this.stepLen) + (groupMaxLen % this.stepLen == 0 ? this.stepLen : groupMaxLen % this.stepLen) - 1);currLen = groupMaxLen % this.stepLen;}else{tagName = String.Format("{0}-{1}", groupMinStart + (i this.stepLen), groupMinStart + (i this.stepLen) + this.stepLen - 1);currLen = this.stepLen;}string tagFullName = String.Format("{0}{1}.{2}", groupNamePrefix, group.Block, tagName);if (!this.readResult.ContainsKey(tagFullName)){bool exists = false;region 判断读取结果标签组的范围是否包括了此标签 比如tagFullName DB5.220-299,在readResult中存在 DB5.200-299,则认为已存在,不需要再添加string[] beginend = null;int begin = 0;int end = 0;string[] startstop = tagFullName.Replace(String.Format("{0}{1}.", groupNamePrefix, group.Block), String.Empty).Split(new char[] { '-' });int start = 0;int stop = 0;bool parseResult = false;if (startstop.Length == 2){parseResult = int.TryParse(startstop[0], out start);if (parseResult){parseResult = int.TryParse(startstop[1], out stop);} }if (parseResult){int existsMinBegin = 0; //已存在标签的最小开始索引int existsMaxEnd = 0; //已存在标签的最大结束索引bool isContinue = true; //标签值是否连续string[] existsTags = this.readResult.Keys.ToArray<string>();foreach (string tag in existsTags){if (tag.StartsWith(String.Format("{0}{1}.", groupNamePrefix, group.Block)) && tag.Contains(".") && tag.Contains("-")){string[] tagname = tag.Split(new char[] { '.' });if (tagname.Length == 2){beginend = tagname[1].Split(new char[] { '-' });if (beginend.Length == 2){parseResult = int.TryParse(beginend[0], out begin);if (parseResult){parseResult = int.TryParse(beginend[1], out end);}region 计算最小开始索引和最大结束索引if (begin < existsMinBegin){existsMinBegin = begin;region 判断标签值是否连续if (existsMaxEnd != 0 && begin != existsMaxEnd + 1){isContinue = false;}endregion}if (end > existsMaxEnd){existsMaxEnd = end;}endregion} }if (parseResult){if (start >= begin && stop <= end){exists = true;break;}if (isContinue){if (start >= existsMinBegin && stop <= existsMaxEnd){exists = true;break;} }} }} }endregionif (!exists){ushort[] groupData = new ushort[currLen];this.readResult[tagFullName] = groupData;Console.WriteLine(tagFullName);} }}//int tagCount = group.Len % this.stepLen == 0 ? group.Len / this.stepLen : group.Len / this.stepLen + 1;//int currLen = 0;//for (int i = 0; i < tagCount; i++)//{// string tagName = String.Empty;// if (tagCount == 1)// {// tagName = String.Format("{0}-{1}", group.Start, group.Start + group.Len - 1);// currLen = group.Len;// }// else if (i == tagCount - 1)// {// tagName = String.Format("{0}-{1}", group.Start + (i this.stepLen), group.Start + (i this.stepLen) + (group.Len % this.stepLen == 0 ? this.stepLen : group.Len % this.stepLen) - 1);// currLen = group.Len % this.stepLen;// }// else// {// tagName = String.Format("{0}-{1}", group.Start + (i this.stepLen), group.Start + (i this.stepLen) + this.stepLen - 1);// currLen = this.stepLen;// }// string tagFullName = String.Format("{0}{1}.{2}", groupNamePrefix, group.Block, tagName);// if (!this.readResult.ContainsKey(tagFullName))// {// short[] groupData = new short[currLen];// this.readResult[tagFullName] = groupData;// }//} }endregionregion 开启内部定时读取if (this.innerReadThread == null){this.innerReadRate = this.Main.ReadHz / 2;this.innerReadThread = new System.Threading.Thread(this.InnerAutoRead);this.innerReadThread.Start();}endregion}return this.State;} }/// <summary>/// 从设备读取数据/// </summary>/// <param name="block">要读取的块号</param>/// <param name="start">要读取的起始字</param>/// <param name="len">要读取的长度</param>/// <param name="buff">读取成功后的输出数据</param>/// <returns>成功返回true,失败返回false</returns>public override bool Read(string block, int start, int len, out object[] buff){lock (this){buff = null;if (this._isClosing){return false;}string readstrflag = String.Format("{0}{1}.{2}-{3}", this.groupNamePrefix, block, start, start + len - 1);System.Text.StringBuilder sbtaglength = new System.Text.StringBuilder();string startTag = String.Empty;string groupName = String.Format("{0}{1}", this.groupNamePrefix, block); //要读取的OPCServer块List<ushort> groupData = new List<ushort>();List<string> groupTagNames = new List<string>();int startIndex = 0;try{if (!Open()){return false;}//return true;string[] keys = this.readResult.Keys.ToArray<string>();foreach (string key in keys){if (key.StartsWith(groupName) && key.Replace(String.Format("{0}.", groupName), String.Empty).Contains("-")){groupTagNames.Add(key);} }groupTagNames.Sort(); //对块标签进行排序foreach (string key in groupTagNames){if (String.IsNullOrEmpty(startTag)){startTag = key.Replace(String.Format("{0}.", groupName), String.Empty);}ushort[] values;if (this.readResult[key] is ushort[]){values = this.readResult[key] as ushort[];}else{values = new ushort[] { (ushort)this.readResult[key] };}sbtaglength.Append(String.Format("tagName={0}, buff length = {1}", key, values.Length));groupData.AddRange(values);}buff = new object[len];if (!String.IsNullOrEmpty(startTag)){string strStartIndex = startTag.Substring(0, startTag.IndexOf("-"));int.TryParse(strStartIndex, out startIndex);startIndex = start - startIndex;Array.Copy(groupData.ToArray(), startIndex, buff, 0, buff.Length);}else{}return true;}catch (Exception ex){Console.WriteLine(String.Join(";", groupTagNames.ToArray<string>()));Console.WriteLine("data length = " + groupData.Count);Console.WriteLine(this.Name + "读取失败[" + readstrflag + "]:" + ex.Message);Console.WriteLine(sbtaglength.ToString());this.State = false;return false;} }}/// <summary>/// 写入数据到设备/// </summary>/// <param name="block">要写入的块号</param>/// <param name="start">要写入的起始字</param>/// <param name="buff">要写如的数据</param>/// <returns>成功返回true,失败返回false</returns>public override bool Write(int block, int start, object[] buff){bool result = true;lock (this){try{if (this._isClosing){return false;}if (!Open()){return false;}bool isWrite = false;region 按标签变量写入string itemId = "";foreach (Equips.BaseInfo.Group group in this.Group.Values){if (group.Block == block.ToString()){foreach (Equips.BaseInfo.Data data in group.Data.Values){if (group.Start + data.Start == start && data.Len == buff.Length){if (this.dicTags.ContainsKey(data.Name)){itemId = this.dicTags[data.Name];}break;} }} }if (!String.IsNullOrEmpty(itemId)){UInt16[] intBuff = new UInt16[buff.Length];for (int i = 0; i < intBuff.Length; i++){intBuff[i] = 0;if (!UInt16.TryParse(buff[i].ToString(), out intBuff[i])){Console.WriteLine("在写入OPCUA标签时把buff中的元素转为UInt16类型失败!");} }result = this.myOpcHelper.WriteUInt16(itemId, intBuff);if (!result){Console.WriteLine(String.Format("标签变量[{0}]写入失败!", itemId));return false;}else{Console.WriteLine("按标签变量写入..." + itemId);isWrite = true;} }if (isWrite){return true;}endregionregion 按块写入region 先读取相应标签数数据string startTag = String.Empty;string groupName = String.Format("{0}{1}", this.groupNamePrefix, block); //要读取的OPCServer块List<ushort> groupData = new List<ushort>();string[] keys = readResult.Keys.Where(o => o.StartsWith(groupName) && o.Contains("-")).OrderBy(c => c).ToArray<string>();foreach (string key in keys){if (String.IsNullOrEmpty(startTag)){startTag = key.Replace(String.Format("{0}.", groupName), String.Empty);}string[] beginEnd = key.Replace(String.Format("{0}.", groupName), String.Empty).Split(new char[] { '-' });if (beginEnd.Length != 2){Console.WriteLine(String.Format("标签变量[{0}]未按约定方式命名,请按[DB块号].[起始字-结束字]方式标签变量进行命名!", String.Format("{0}.{1}", key)));return false;}int begin = 0;int end = 0;int.TryParse(beginEnd[0], out begin);int.TryParse(beginEnd[1], out end);region 写入之前,先读取一下PLC的值if ((start >= begin && start <= end) || ((start + buff.Length - 1) >= begin && (start + buff.Length - 1) <= end) || (start < begin && (start + buff.Length - 1) > end)){this.ReadTag(key);if (this.readResult.ContainsKey(key) && this.readResult[key] is Array){Console.WriteLine("read = " + key);groupData.AddRange(this.readResult[key] as ushort[]);}else{Console.WriteLine(String.Format("读取结果中不包含标签变量[{0}]的值!", String.Format("{0}", key)));} }else{if (this.readResult.ContainsKey(key) && this.readResult[key] is Array){Console.WriteLine("no read = " + key);groupData.AddRange(this.readResult[key] as ushort[]);} }endregion}endregionif (String.IsNullOrEmpty(startTag)){Console.WriteLine("写入失败,未在OPCUAserver中找到对应的标签,block = {0}, start = {1}, len = {2}", block, start, buff.Length);return false;}region 更新标签中对应的数据后,再写回OPCServerint startIndex = 0;string strStartIndex = startTag.Substring(0, startTag.IndexOf("-"));int.TryParse(strStartIndex, out startIndex);startIndex = start - startIndex;ushort[] newDataBuffer = groupData.ToArray();for (int i = 0; i < buff.Length; i++){ushort svalue = 0;ushort.TryParse(buff[i].ToString(), out svalue);newDataBuffer[startIndex + i] = svalue;}int index = 0;string[] keys2 = readResult.Keys.Where(o => o.StartsWith(groupName) && o.Contains("-")).OrderBy(c => c).ToArray<string>();foreach (string key2 in keys2){string[] beginEnd = key2.Replace(String.Format("{0}.", groupName), String.Empty).Split(new char[] { '-' });if (beginEnd.Length != 2){Console.WriteLine(String.Format("标签变量[{0}]未按约定方式命名,请按[DB块号].[起始字-结束字]方式标签变量进行命名!", String.Format("{0}", key2)));return false;}int begin = 0;int end = 0;int.TryParse(beginEnd[0], out begin);int.TryParse(beginEnd[1], out end);if ((start >= begin && start <= end) || ((start + buff.Length - 1) >= begin && (start + buff.Length - 1) <= end) || (start < begin && (start + buff.Length - 1) > end)){//Console.WriteLine("---------------------------------------------------------");//Console.WriteLine("start = " + start);//Console.WriteLine("start + buff.Length - 1 = " + (start + buff.Length -1));//Console.WriteLine("begin = " + begin);//Console.WriteLine("end = " + end);//Console.WriteLine("---------------------------------------------------------");if (!this.dicTags.ContainsKey(key2)){Console.WriteLine(String.Format("写入失败:标签变量[{0}]在OpcUA Server中未定义!", String.Format("{0}", key2)));return false;}int len = (this.readResult[key2] as ushort[]).Length;ushort[] tagDataBuff = new ushort[len];//Console.WriteLine("newDataBuff");//Console.WriteLine(String.Join(",", newDataBuffer));//Console.WriteLine("index = " + index);//Console.WriteLine("tagDataBuff.Length = " + tagDataBuff.Length);//Array.Copy(newDataBuffer, begin, tagDataBuff, 0, tagDataBuff.Length);int existsMinBegin = this.GetExistsMinBeginByBlock(block.ToString());Array.Copy(newDataBuffer, begin - existsMinBegin, tagDataBuff, 0, tagDataBuff.Length);index += tagDataBuff.Length;//Console.WriteLine("Write " + key2);//Console.WriteLine(String.Join(",", tagDataBuff));//Console.WriteLine("写入标签:" + this.dicTags[key2]);result = this.myOpcHelper.WriteUInt16(this.dicTags[key2], tagDataBuff);if (!result){Console.WriteLine(String.Format("向标签变量[{0}]中写入值失败!", String.Format("{0}", key2)));return false;}else{this.ReadTag(key2);Console.WriteLine("写入...");}//Console.WriteLine("---------------------------------------------------------");} }endregionendregionreturn result;}catch (Exception ex){Console.WriteLine(this.Name + "写入失败:" + ex.Message);return false;} }}/// <summary>/// 关闭方法,断开与设备的连接释放资源/// </summary>public override void Close(){try{this._isClosing = true;System.Threading.Thread.Sleep(this.Main.ReadHz);if (this.innerReadThread != null){this.innerReadThread.Abort();this.innerReadThread = null;} }catch (Exception ex){Console.WriteLine("关闭内部读取OPCUA线程异常:" + ex.Message);}try{if (this.myOpcHelper != null){this.myOpcHelper.Close();this.myOpcHelper = null;this.State = false;this._isOpen = false;} }catch (Exception ex){Console.WriteLine("关于与OPCUA服务连接异常:" + ex.Message);} }endregionregion 辅助方法/// <summary>/// 获取某个数据块标签的最小开始索引/// </summary>/// <param name="block">块号</param>/// <returns>返回数据块标签的最小开始索引</returns>private int GetExistsMinBeginByBlock(string block){int existsMinBegin = 99999; //已存在标签的最小开始索引int existsMaxEnd = 0; //已存在标签的最大结束索引bool isContinue = true; //标签值是否连续string[] existsTags = this.readResult.Keys.ToArray<string>();string[] beginend = null;bool parseResult = false;int begin = 0;int end = 0;foreach (string tag in existsTags){if (tag.StartsWith(String.Format("{0}{1}.", groupNamePrefix, block)) && tag.Contains(".") && tag.Contains("-")){string[] tagname = tag.Split(new char[] { '.' });if (tagname.Length == 2){beginend = tagname[1].Split(new char[] { '-' });if (beginend.Length == 2){parseResult = int.TryParse(beginend[0], out begin);if (parseResult){parseResult = int.TryParse(beginend[1], out end);}region 计算最小开始索引和最大结束索引if (begin < existsMinBegin){existsMinBegin = begin;region 判断标签值是否连续if (existsMaxEnd != 0 && begin != existsMaxEnd + 1){isContinue = false;}endregion}if (end > existsMaxEnd){existsMaxEnd = end;}endregion} }if (parseResult){//} }}return existsMinBegin;}/// <summary>/// 读取标签/// </summary>/// <param name="tagName"></param>private void ReadTag(string tagName){UInt16[] buff = null;if (this.dicTags.ContainsKey(tagName)){if (this.myOpcHelper.ReadUInt16(this.dicTags[tagName], out buff)){//Console.WriteLine("tagName={0}, buff length = {1}", tagName, buff.Length);if (this.readResult.ContainsKey(tagName)){this.readResult[tagName] = buff;}else{this.readResult.Add(tagName, buff);} }else{Console.WriteLine("Mesnac.Equip.OPC.OpcUa.OPCUA.Equip.ReadTag Exception 读取标签:[{0}]失败!", tagName);} }else{Console.WriteLine("Mesnac.Equip.OPC.OpcUa.OPCUA.Equip.ReadTag Exception OPCUA Server中未定义此标签:[{0}]!", tagName);} }/// <summary>/// 内部自动读取方法/// </summary>private void InnerAutoRead(){while (this._isOpen && this._isClosing == false){try{if (this.myOpcHelper == null){this._isClosing = true;this.State = false;return;}lock (this){string[] keys = this.readResult.Keys.ToArray<string>();foreach (string key in keys){this.ReadTag(key);} }System.Threading.Thread.Sleep(this.innerReadRate);}catch (Exception ex){Console.WriteLine("Mesnac.Equip.OPC.OpcUa.OPCUA.Equip.InnerAutoRead Exception : " + ex.Message);} }this.innerReadThread = null;}endregionregion 析构方法~Equip(){this.Close();}endregion} } 代码下载 代码下载 本篇文章为转载内容。原文链接:https://blog.csdn.net/zlbdmm/article/details/96714776。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-05-10 18:43:00
269
转载
转载文章
...S Code(Visual Studio Code)以其强大的扩展生态系统、跨平台支持以及对现代Web技术(如Vue.js、React和Angular)的一流支持而备受开发者青睐,已逐渐成为主流的前端开发IDE之一。 与此同时,随着WebAssembly等技术的发展,Web开发工具正不断拓展边界,以适应更复杂的应用场景。例如,JetBrains公司推出的Theia项目,是一个基于浏览器的开源IDE框架,旨在为云端开发提供轻量级且功能全面的解决方案。 另外,在响应式设计和移动优先战略的推动下,诸如Figma这样的实时协作UI/UX设计工具也在Web开发流程中扮演了重要角色,使得设计师与开发者之间的协同工作更为高效便捷。 对于JavaScript生态,Chrome DevTools及其配套的Lighthouse性能审计工具也不断升级,提供了更详尽的网页性能分析报告及优化建议,帮助开发者打造高性能的Web应用。 此外,Web组件标准日益成熟,Polymer、Stencil等库和框架助力开发者快速构建可复用的自定义元素,相关开发工具和教程资源也越来越丰富。 综上所述,无论是代码编辑器、调试工具还是设计协作平台,Web开发领域的工具链正在不断创新和完善,以满足日益增长的多元化开发需求,为广大开发者提供了更加先进、高效的开发环境。
2023-02-12 17:23:46
136
转载
转载文章
...站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 写在最前边: Dell这个牌子的一个主要特征就是兼容性极差,硬件留给你的操作空间极小,很离谱,很想跟戴尔绕着走 这个结论的出现就得从我的G15说起了:当时买的时候只有一个固态硬盘,想加装一个,然后就买了当时的PCIE4.0协议的三星980pro,后来发现硬盘口只有原厂硬盘的硬盘口支持4.0协议,这还没完。硬盘装上去之后,暂时看不出什么异常,但是电脑经常会卡死,就是屏幕亮着啥也点不动,B站也一堆改装翻车的,后来把三星980pro换到了3.0的口,问题就没在发生过了。从此Dell的不兼容性就给我留下了深深的印象。 最近,我们办公室的服务器噪音巨大,从开机键按下的一刻起就是飞机起飞状态。一看牌子:好家伙,Dell的!!!那没事了…Giao~ 还是抱有一丝希望地去网上搜了一下,果然是因为硬件设备的原因,T640无法识别3090,进而无法自适应调整风扇转速。Dell,不愧是你! 经过较为漫长的搜索调试,最后终于对风扇转速实现了较为方便的手动控制,下面对这个过程进行一下梳理。 -------------------------------------------------------------------------------------分界线------------------------------------------------------------------------------------- 1.首先是参考了这一篇文章:https://zhuanlan.zhihu.com/p/336990051 主要介绍了两种方式解决这个问题: 使用racadm温度调控,但是配置教程是Ubuntu16.04下的,过程中有些linux语句在18.04中运行报错,本身对linux就不是很熟,然后我果断放弃。 更新BIOS 和IDRAC,他2022年3月3日通过更新版本,实现了风扇转速的控制,但是我2022年6月,按照他给的下载版本,更新了,发现没用啊??!!回退版本没用,更新版本也没用,就很离谱,难道因为他是2080ti,我是3090的问题??操作步骤如下: 参考该博客对服务器IDRAC配置 https://www.dell.com/support/kbdoc/zh-cn/000177212,查看解决方案中的开机自检期间为F2进行配置 配置好后,在服务器后后面有个IDRAC的网线插口,用网线与笔记本连接,连接成功后会显示未识别网络(如果是红叉的话是没有连接成功,检查上一步,尝试关机重启等),修改IP地址,跟上一步设置的服务器IP在同一网段,不是同一IP!!,比如服务器是192.168.0.120,笔记本可以设置192.168.0.100。(https://new.qq.com/omn/20210119/20210119A01ROV00.html) IE浏览器打开192.168.0.100网址,提示不安全,然后忽略掉,输入账号密码就可以进去了 进去后在下图位置,上传更新文件进行安装。 2.后面又看到一篇博客:https://blog.csdn.net/qq_36810544/article/details/115734795这篇博客比上边那篇早,应该是有参考吧,说是更新版本就行了,然并卵啊,可能是因为他是Ubuntu20.04,我是18.04的原因? 3.最后没招了,用IPMITOOL手动调节吧,参考了博客:https://blog.51cto.com/u_15072918/4392813 这篇博客也是更新后仍然无法识别3090(实际上我下的新版本的IDRAC是可以识别出有GPU的,但是还是显示不可用哇),所以就把IDRAC的版本回退到3.30以下使用IPMITOOL进行行手动调节转速了。 具体步骤如下: 将IDRAC回退到3.30版本,下载地址:https://www.dell.com/support/home/zh-cn/drivers/driversdetails 有的版本IDRAC可能需要把IMPI取消禁用,就在笔记本访问的IP地址的网页里修改即可,应该是在IDRAC设置中,没找到的话应该是不需要操作。 下载IPMITOOLWIN版本程序后解压,终端cd进入该文件夹,然后运行ipmitool命令: 关闭自动控制:ipmitool -I lanplus -U 用户名 -P 密码 -H 服务器地址 raw 0x30 0x30 0x01 0x00 设置风扇转速:ipmitool -I lanplus -U 用户名 -P 密码 -H 192.168.0.120 raw 0x30 0x30 0x02 0xff 0x64 ,最后两位对应16进制的风扇转速。64对应100%。 3.转速现在是可以手动调节了,但是每次都要执行终端命令太麻烦了,然后我写了一个小的gui界面,可以更方便地对风扇转速进行调节。界面如下,可以通过+和-增加和降低风速,也可以设定数值进行Set。 为了防止过热,最低风扇转速设置成了30%。需要注意:这个文件中IDRAC的IP必须是192.168.0.120才可以。 本文就先写到这里了,调节软件如果有需求的话可以后续上传,我在程序中也放了IPMITOOLWIN的文件,不需要再进行下载。有更好的解决方法也欢迎评论区分享。 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_42686221/article/details/125478351。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-02-24 14:29:07
172
转载
转载文章
...Windows系统下配置MPICH进行并行编程的经验后,我们可以进一步探索当前并行计算技术的发展趋势以及MPI(Message Passing Interface)在现代高性能计算领域中的应用现状。 近年来,随着大数据和人工智能等领域的飞速发展,对计算能力的需求日益增长,MPI作为并行计算的重要通信接口标准,在解决大规模科学计算、机器学习等问题上发挥着关键作用。最新版本的MPICH已支持更多的优化策略和特性,如更好的多核CPU利用、对GPU加速计算的支持以及更高效的网络传输协议,以适应不断变化的高性能计算环境需求。 同时,微软Azure云平台和AWS Amazon EC2等云服务提供商也相继推出了预装MPI的高性能计算实例,用户无需在本地搭建复杂环境,即可直接在云端进行MPI并行程序开发与测试,极大地降低了使用门槛,促进了并行计算技术的普及与应用。 另外,随着跨平台开发需求的增长,开源社区也在积极推动MPICH在Linux、macOS等其他操作系统上的兼容性和性能优化。例如,Microsoft Research团队合作推出的Open MPI项目,旨在提供一个高度可扩展且跨平台的MPI实现,为开发者提供更多选择和灵活性。 此外,对于希望深入了解MPI编程原理及其实战技巧的读者,可以参考《Using MPI - 3rd Edition》这本书,作者详细解析了MPI的各种函数用法,并提供了大量实例代码,是MPI编程入门到精通的绝佳教程资源。 综上所述,无论是从MPI技术的最新进展、云计算环境下的并行计算解决方案,还是深入学习MPI编程的专业书籍推荐,都为那些想要在并行计算领域持续探索和实践的读者提供了丰富的延伸阅读内容。
2023-04-09 11:52:38
113
转载
转载文章
...站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 注:本文是【ASP.NET Identity系列教程】的第三篇。本系列教程详细、完整、深入地介绍了微软的ASP.NET Identity技术,描述了如何运用ASP.NET Identity实现应用程序的用户管理,以及实现应用程序的认证与授权等相关技术,译者希望本系列教程能成为掌握ASP.NET Identity技术的一份完整而有价值的资料。读者若是能够按照文章的描述,一边阅读、一边实践、一边理解,定能有意想不到的巨大收获!希望本系列博文能够得到广大园友的高度推荐。 15 Advanced ASP.NET Identity 15 ASP.NET Identity高级技术 In this chapter, I finish my description of ASP.NET Identity by showing you some of the advanced features it offers. I demonstrate how you can extend the database schema by defining custom properties on the user class and how to use database migrations to apply those properties without deleting the data in the ASP.NET Identity database. I also explain how ASP.NET Identity supports the concept of claims and demonstrates how they can be used to flexibly authorize access to action methods. I finish the chapter—and the book—by showing you how ASP.NET Identity makes it easy to authenticate users through third parties. I demonstrate authentication with Google accounts, but ASP.NET Identity has built-in support for Microsoft, Facebook, and Twitter accounts as well. Table 15-1 summarizes this chapter. 本章将完成对ASP.NET Identity的描述,向你展示它所提供的一些高级特性。我将演示,你可以扩展ASP.NET Identity的数据库架构,其办法是在用户类上定义一些自定义属性。也会演示如何使用数据库迁移,这样可以运用自定义属性,而不必删除ASP.NET Identity数据库中的数据。还会解释ASP.NET Identity如何支持声明(Claims)概念,并演示如何将它们灵活地用来对动作方法进行授权访问。最后向你展示ASP.NET Identity很容易通过第三方部件来认证用户,以此结束本章以及本书。将要演示的是使用Google账号认证,但ASP.NET Identity对于Microsoft、Facebook以及Twitter账号,都有内建的支持。表15-1是本章概要。 Table 15-1. Chapter Summary 表15-1. 本章概要 Problem 问题 Solution 解决方案 Listing 清单号 Store additional information about users. 存储用户的附加信息 Define custom user properties. 定义自定义用户属性 1–3, 8–11 Update the database schema without deleting user data. 更新数据库架构而不删除用户数据 Perform a database migration. 执行数据库迁移 4–7 Perform fine-grained authorization. 执行细粒度授权 Use claims. 使用声明(Claims) 12–14 Add claims about a user. 添加用户的声明(Claims) Use the ClaimsIdentity.AddClaims method. 使用ClaimsIdentity.AddClaims方法 15–19 Authorize access based on claim values. 基于声明(Claims)值授权访问 Create a custom authorization filter attribute. 创建一个自定义的授权过滤器注解属性 20–21 Authenticate through a third party. 通过第三方认证 Install the NuGet package for the authentication provider, redirect requests to that provider, and specify a callback URL that creates the user account. 安装认证提供器的NuGet包,将请求重定向到该提供器,并指定一个创建用户账号的回调URL。 22–25 15.1 Preparing the Example Project 15.1 准备示例项目 In this chapter, I am going to continue working on the Users project I created in Chapter 13 and enhanced in Chapter 14. No changes to the application are required, but start the application and make sure that there are users in the database. Figure 15-1 shows the state of my database, which contains the users Admin, Alice, Bob, and Joe from the previous chapter. To check the users, start the application and request the /Admin/Index URL and authenticate as the Admin user. 本章打算继续使用第13章创建并在第14章增强的Users项目。对应用程序无需做什么改变,但需要启动应用程序,并确保数据库中有一些用户。图15-1显示了数据库的状态,它含有上一章的用户Admin、Alice、Bob以及Joe。为了检查用户,请启动应用程序,请求/Admin/Index URL,并以Admin用户进行认证。 Figure 15-1. The initial users in the Identity database 图15-1. Identity数据库中的最初用户 I also need some roles for this chapter. I used the RoleAdmin controller to create roles called Users and Employees and assigned the users to those roles, as described in Table 15-2. 本章还需要一些角色。我用RoleAdmin控制器创建了角色Users和Employees,并为这些角色指定了一些用户,如表15-2所示。 Table 15-2. The Types of Web Forms Code Nuggets 表15-2. 角色及成员(作者将此表的标题写错了——译者注) Role 角色 Members 成员 Users Alice, Joe Employees Alice, Bob Figure 15-2 shows the required role configuration displayed by the RoleAdmin controller. 图15-2显示了由RoleAdmin控制器所显示出来的必要的角色配置。 Figure 15-2. Configuring the roles required for this chapter 图15-2. 配置本章所需的角色 15.2 Adding Custom User Properties 15.2 添加自定义用户属性 When I created the AppUser class to represent users in Chapter 13, I noted that the base class defined a basic set of properties to describe the user, such as e-mail address and telephone number. Most applications need to store more information about users, including persistent application preferences and details such as addresses—in short, any data that is useful to running the application and that should last between sessions. In ASP.NET Membership, this was handled through the user profile system, but ASP.NET Identity takes a different approach. 我在第13章创建AppUser类来表示用户时曾做过说明,基类定义了一组描述用户的基本属性,如E-mail地址、电话号码等。大多数应用程序还需要存储用户的更多信息,包括持久化应用程序爱好以及地址等细节——简言之,需要存储对运行应用程序有用并且在各次会话之间应当保持的任何数据。在ASP.NET Membership中,这是通过用户资料(User Profile)系统来处理的,但ASP.NET Identity采取了一种不同的办法。 Because the ASP.NET Identity system uses Entity Framework to store its data by default, defining additional user information is just a matter of adding properties to the user class and letting the Code First feature create the database schema required to store them. Table 15-3 puts custom user properties in context. 因为ASP.NET Identity默认是使用Entity Framework来存储其数据的,定义附加的用户信息只不过是给用户类添加属性的事情,然后让Code First特性去创建需要存储它们的数据库架构即可。表15-3描述了自定义用户属性的情形。 Table 15-3. Putting Cusotm User Properties in Context 表15-3. 自定义用户属性的情形 Question 问题 Answer 回答 What is it? 什么是自定义用户属性? Custom user properties allow you to store additional information about your users, including their preferences and settings. 自定义用户属性让你能够存储附加的用户信息,包括他们的爱好和设置。 Why should I care? 为何要关心它? A persistent store of settings means that the user doesn’t have to provide the same information each time they log in to the application. 设置的持久化存储意味着,用户不必每次登录到应用程序时都提供同样的信息。 How is it used by the MVC framework? 在MVC框架中如何使用它? This feature isn’t used directly by the MVC framework, but it is available for use in action methods. 此特性不是由MVC框架直接使用的,但它在动作方法中使用是有效的。 15.2.1 Defining Custom Properties 15.2.1 定义自定义属性 Listing 15-1 shows how I added a simple property to the AppUser class to represent the city in which the user lives. 清单15-1演示了如何给AppUser类添加一个简单的属性,用以表示用户生活的城市。 Listing 15-1. Adding a Property in the AppUser.cs File 清单15-1. 在AppUser.cs文件中添加属性 using System;using Microsoft.AspNet.Identity.EntityFramework;namespace Users.Models { public enum Cities {LONDON, PARIS, CHICAGO}public class AppUser : IdentityUser {public Cities City { get; set; } }} I have defined an enumeration called Cities that defines values for some large cities and added a property called City to the AppUser class. To allow the user to view and edit their City property, I added actions to the Home controller, as shown in Listing 15-2. 这里定义了一个枚举,名称为Cities,它定义了一些大城市的值,另外给AppUser类添加了一个名称为City的属性。为了让用户能够查看和编辑City属性,给Home控制器添加了几个动作方法,如清单15-2所示。 Listing 15-2. Adding Support for Custom User Properties in the HomeController.cs File 清单15-2. 在HomeController.cs文件中添加对自定义属性的支持 using System.Web.Mvc;using System.Collections.Generic;using System.Web;using System.Security.Principal;using System.Threading.Tasks;using Users.Infrastructure;using Microsoft.AspNet.Identity;using Microsoft.AspNet.Identity.Owin;using Users.Models;namespace Users.Controllers {public class HomeController : Controller {[Authorize]public ActionResult Index() {return View(GetData("Index"));}[Authorize(Roles = "Users")]public ActionResult OtherAction() {return View("Index", GetData("OtherAction"));}private Dictionary<string, object> GetData(string actionName) {Dictionary<string, object> dict= new Dictionary<string, object>();dict.Add("Action", actionName);dict.Add("User", HttpContext.User.Identity.Name);dict.Add("Authenticated", HttpContext.User.Identity.IsAuthenticated);dict.Add("Auth Type", HttpContext.User.Identity.AuthenticationType);dict.Add("In Users Role", HttpContext.User.IsInRole("Users"));return dict;} [Authorize]public ActionResult UserProps() {return View(CurrentUser);}[Authorize][HttpPost]public async Task<ActionResult> UserProps(Cities city) {AppUser user = CurrentUser;user.City = city;await UserManager.UpdateAsync(user);return View(user);}private AppUser CurrentUser {get {return UserManager.FindByName(HttpContext.User.Identity.Name);} }private AppUserManager UserManager {get {return HttpContext.GetOwinContext().GetUserManager<AppUserManager>();} }} } I added a CurrentUser property that uses the AppUserManager class to retrieve an AppUser instance to represent the current user. I pass the AppUser object as the view model object in the GET version of the UserProps action method, and the POST method uses it to update the value of the new City property. Listing 15-3 shows the UserProps.cshtml view, which displays the City property value and contains a form to change it. 我添加了一个CurrentUser属性,它使用AppUserManager类接收了表示当前用户的AppUser实例。在GET版本的UserProps动作方法中,传递了这个AppUser对象作为视图模型。而在POST版的方法中用它更新了City属性的值。清单15-3显示了UserProps.cshtml视图,它显示了City属性的值,并包含一个修改它的表单。 Listing 15-3. The Contents of the UserProps.cshtml File in the Views/Home Folder 清单15-3. Views/Home文件夹中UserProps.cshtml文件的内容 @using Users.Models@model AppUser@{ ViewBag.Title = "UserProps";}<div class="panel panel-primary"><div class="panel-heading">Custom User Properties</div><table class="table table-striped"><tr><th>City</th><td>@Model.City</td></tr></table></div> @using (Html.BeginForm()) {<div class="form-group"><label>City</label>@Html.DropDownListFor(x => x.City, new SelectList(Enum.GetNames(typeof(Cities))))</div><button class="btn btn-primary" type="submit">Save</button>} Caution Don’t start the application when you have created the view. In the sections that follow, I demonstrate how to preserve the contents of the database, and if you start the application now, the ASP.NET Identity users will be deleted. 警告:创建了视图之后不要启动应用程序。在以下小节中,将演示如何保留数据库的内容,如果现在启动应用程序,将会删除ASP.NET Identity的用户。 15.2.2 Preparing for Database Migration 15.2.2 准备数据库迁移 The default behavior for the Entity Framework Code First feature is to drop the tables in the database and re-create them whenever classes that drive the schema have changed. You saw this in Chapter 14 when I added support for roles: When the application was started, the database was reset, and the user accounts were lost. Entity Framework Code First特性的默认行为是,一旦修改了派生数据库架构的类,便会删除数据库中的数据表,并重新创建它们。在第14章可以看到这种情况,在我添加角色支持时:当重启应用程序后,数据库被重置,用户账号也丢失。 Don’t start the application yet, but if you were to do so, you would see a similar effect. Deleting data during development is usually not a problem, but doing so in a production setting is usually disastrous because it deletes all of the real user accounts and causes a panic while the backups are restored. In this section, I am going to demonstrate how to use the database migration feature, which updates a Code First schema in a less brutal manner and preserves the existing data it contains. 不要启动应用程序,但如果你这么做了,会看到类似的效果。在开发期间删除数据没什么问题,但如果在产品设置中这么做了,通常是灾难性的,因为它会删除所有真实的用户账号,而备份恢复是很痛苦的事。在本小节中,我打算演示如何使用数据库迁移特性,它能以比较温和的方式更新Code First的架构,并保留架构中的已有数据。 The first step is to issue the following command in the Visual Studio Package Manager Console: 第一个步骤是在Visual Studio的“Package Manager Console(包管理器控制台)”中发布以下命令: Enable-Migrations –EnableAutomaticMigrations This enables the database migration support and creates a Migrations folder in the Solution Explorer that contains a Configuration.cs class file, the contents of which are shown in Listing 15-4. 它启用了数据库的迁移支持,并在“Solution Explorer(解决方案资源管理器)”创建一个Migrations文件夹,其中含有一个Configuration.cs类文件,内容如清单15-4所示。 Listing 15-4. The Contents of the Configuration.cs File 清单15-4. Configuration.cs文件的内容 namespace Users.Migrations {using System;using System.Data.Entity;using System.Data.Entity.Migrations;using System.Linq;internal sealed class Configuration: DbMigrationsConfiguration<Users.Infrastructure.AppIdentityDbContext> {public Configuration() {AutomaticMigrationsEnabled = true;ContextKey = "Users.Infrastructure.AppIdentityDbContext";}protected override void Seed(Users.Infrastructure.AppIdentityDbContext context) {// This method will be called after migrating to the latest version.// 此方法将在迁移到最新版本时调用// You can use the DbSet<T>.AddOrUpdate() helper extension method// to avoid creating duplicate seed data. E.g.// 例如,你可以使用DbSet<T>.AddOrUpdate()辅助器方法来避免创建重复的种子数据//// context.People.AddOrUpdate(// p => p.FullName,// new Person { FullName = "Andrew Peters" },// new Person { FullName = "Brice Lambson" },// new Person { FullName = "Rowan Miller" }// );//} }} Tip You might be wondering why you are entering a database migration command into the console used to manage NuGet packages. The answer is that the Package Manager Console is really PowerShell, which is a general-purpose tool that is mislabeled by Visual Studio. You can use the console to issue a wide range of helpful commands. See http://go.microsoft.com/fwlink/?LinkID=108518 for details. 提示:你可能会觉得奇怪,为什么要在管理NuGet包的控制台中输入数据库迁移的命令?答案是“Package Manager Console(包管理控制台)”是真正的PowerShell,这是Visual studio冒用的一个通用工具。你可以使用此控制台发送大量的有用命令,详见http://go.microsoft.com/fwlink/?LinkID=108518。 The class will be used to migrate existing content in the database to the new schema, and the Seed method will be called to provide an opportunity to update the existing database records. In Listing 15-5, you can see how I have used the Seed method to set a default value for the new City property I added to the AppUser class. (I have also updated the class file to reflect my usual coding style.) 这个类将用于把数据库中的现有内容迁移到新的数据库架构,Seed方法的调用为更新现有数据库记录提供了机会。在清单15-5中可以看到,我如何用Seed方法为新的City属性设置默认值,City是添加到AppUser类中自定义属性。(为了体现我一贯的编码风格,我对这个类文件也进行了更新。) Listing 15-5. Managing Existing Content in the Configuration.cs File 清单15-5. 在Configuration.cs文件中管理已有内容 using System.Data.Entity.Migrations;using Microsoft.AspNet.Identity;using Microsoft.AspNet.Identity.EntityFramework;using Users.Infrastructure;using Users.Models;namespace Users.Migrations {internal sealed class Configuration: DbMigrationsConfiguration<AppIdentityDbContext> {public Configuration() {AutomaticMigrationsEnabled = true;ContextKey = "Users.Infrastructure.AppIdentityDbContext";}protected override void Seed(AppIdentityDbContext context) {AppUserManager userMgr = new AppUserManager(new UserStore<AppUser>(context));AppRoleManager roleMgr = new AppRoleManager(new RoleStore<AppRole>(context)); string roleName = "Administrators";string userName = "Admin";string password = "MySecret";string email = "admin@example.com";if (!roleMgr.RoleExists(roleName)) {roleMgr.Create(new AppRole(roleName));}AppUser user = userMgr.FindByName(userName);if (user == null) {userMgr.Create(new AppUser { UserName = userName, Email = email },password);user = userMgr.FindByName(userName);}if (!userMgr.IsInRole(user.Id, roleName)) {userMgr.AddToRole(user.Id, roleName);}foreach (AppUser dbUser in userMgr.Users) {dbUser.City = Cities.PARIS;}context.SaveChanges();} }} You will notice that much of the code that I added to the Seed method is taken from the IdentityDbInit class, which I used to seed the database with an administration user in Chapter 14. This is because the new Configuration class added to support database migrations will replace the seeding function of the IdentityDbInit class, which I’ll update shortly. Aside from ensuring that there is an admin user, the statements in the Seed method that are important are the ones that set the initial value for the City property I added to the AppUser class, as follows: 你可能会注意到,添加到Seed方法中的许多代码取自于IdentityDbInit类,在第14章中我用这个类将管理用户植入了数据库。这是因为这个新添加的、用以支持数据库迁移的Configuration类,将代替IdentityDbInit类的种植功能,我很快便会更新这个类。除了要确保有admin用户之外,在Seed方法中的重要语句是那些为AppUser类的City属性设置初值的语句,如下所示: ...foreach (AppUser dbUser in userMgr.Users) { dbUser.City = Cities.PARIS;}context.SaveChanges();... You don’t have to set a default value for new properties—I just wanted to demonstrate that the Seed method in the Configuration class can be used to update the existing user records in the database. 你不一定要为新属性设置默认值——这里只是想演示Configuration类中的Seed方法,可以用它更新数据库中的已有用户记录。 Caution Be careful when setting values for properties in the Seed method for real projects because the values will be applied every time you change the schema, overriding any values that the user has set since the last schema update was performed. I set the value of the City property just to demonstrate that it can be done. 警告:在用于真实项目的Seed方法中为属性设置值时要小心,因为你每一次修改架构时,都会运用这些值,这会将自执行上一次架构更新之后,用户设置的任何数据覆盖掉。这里设置City属性的值只是为了演示它能够这么做。 Changing the Database Context Class 修改数据库上下文类 The reason that I added the seeding code to the Configuration class is that I need to change the IdentityDbInit class. At present, the IdentityDbInit class is derived from the descriptively named DropCreateDatabaseIfModelChanges<AppIdentityDbContext> class, which, as you might imagine, drops the entire database when the Code First classes change. Listing 15-6 shows the changes I made to the IdentityDbInit class to prevent it from affecting the database. 在Configuration类中添加种植代码的原因是我需要修改IdentityDbInit类。此时,IdentityDbInit类派生于描述性命名的DropCreateDatabaseIfModelChanges<AppIdentityDbContext> 类,和你相像的一样,它会在Code First类改变时删除整个数据库。清单15-6显示了我对IdentityDbInit类所做的修改,以防止它影响数据库。 Listing 15-6. Preventing Database Schema Changes in the AppIdentityDbContext.cs File 清单15-6. 在AppIdentityDbContext.cs文件是阻止数据库架构变化 using System.Data.Entity;using Microsoft.AspNet.Identity.EntityFramework;using Users.Models;using Microsoft.AspNet.Identity; namespace Users.Infrastructure {public class AppIdentityDbContext : IdentityDbContext<AppUser> {public AppIdentityDbContext() : base("IdentityDb") { }static AppIdentityDbContext() {Database.SetInitializer<AppIdentityDbContext>(new IdentityDbInit());}public static AppIdentityDbContext Create() {return new AppIdentityDbContext();} } public class IdentityDbInit : NullDatabaseInitializer<AppIdentityDbContext> {} } I have removed the methods defined by the class and changed its base to NullDatabaseInitializer<AppIdentityDbContext> , which prevents the schema from being altered. 我删除了这个类中所定义的方法,并将它的基类改为NullDatabaseInitializer<AppIdentityDbContext> ,它可以防止架构修改。 15.2.3 Performing the Migration 15.2.3 执行迁移 All that remains is to generate and apply the migration. First, run the following command in the Package Manager Console: 剩下的事情只是生成并运用迁移了。首先,在“Package Manager Console(包管理器控制台)”中执行以下命令: Add-Migration CityProperty This creates a new migration called CityProperty (I like my migration names to reflect the changes I made). A class new file will be added to the Migrations folder, and its name reflects the time at which the command was run and the name of the migration. My file is called 201402262244036_CityProperty.cs, for example. The contents of this file contain the details of how Entity Framework will change the database during the migration, as shown in Listing 15-7. 这创建了一个名称为CityProperty的新迁移(我比较喜欢让迁移的名称反映出我所做的修改)。这会在文件夹中添加一个新的类文件,而且其命名会反映出该命令执行的时间以及迁移名称,例如,我的这个文件名称为201402262244036_CityProperty.cs。该文件的内容含有迁移期间Entity Framework修改数据库的细节,如清单15-7所示。 Listing 15-7. The Contents of the 201402262244036_CityProperty.cs File 清单15-7. 201402262244036_CityProperty.cs文件的内容 namespace Users.Migrations {using System;using System.Data.Entity.Migrations; public partial class Init : DbMigration {public override void Up() {AddColumn("dbo.AspNetUsers", "City", c => c.Int(nullable: false));}public override void Down() {DropColumn("dbo.AspNetUsers", "City");} }} The Up method describes the changes that have to be made to the schema when the database is upgraded, which in this case means adding a City column to the AspNetUsers table, which is the one that is used to store user records in the ASP.NET Identity database. Up方法描述了在数据库升级时,需要对架构所做的修改,在这个例子中,意味着要在AspNetUsers数据表中添加City数据列,该数据表是ASP.NET Identity数据库用来存储用户记录的。 The final step is to perform the migration. Without starting the application, run the following command in the Package Manager Console: 最后一步是执行迁移。无需启动应用程序,只需在“Package Manager Console(包管理器控制台)”中运行以下命令即可: Update-Database –TargetMigration CityProperty The database schema will be modified, and the code in the Configuration.Seed method will be executed. The existing user accounts will have been preserved and enhanced with a City property (which I set to Paris in the Seed method). 这会修改数据库架构,并执行Configuration.Seed方法中的代码。已有用户账号会被保留,且增强了City属性(我在Seed方法中已将其设置为“Paris”)。 15.2.4 Testing the Migration 15.2.4 测试迁移 To test the effect of the migration, start the application, navigate to the /Home/UserProps URL, and authenticate as one of the Identity users (for example, as Alice with the password MySecret). Once authenticated, you will see the current value of the City property for the user and have the opportunity to change it, as shown in Figure 15-3. 为了测试迁移的效果,启动应用程序,导航到/Home/UserProps URL,并以Identity中的用户(例如Alice,口令MySecret)进行认证。一旦已被认证,便会看到该用户City属性的当前值,并可以对其进行修改,如图15-3所示。 Figure 15-3. Displaying and changing a custom user property 图15-3. 显示和个性自定义用户属性 15.2.5 Defining an Additional Property 15.2.5 定义附加属性 Now that database migrations are set up, I am going to define a further property just to demonstrate how subsequent changes are handled and to show a more useful (and less dangerous) example of using the Configuration.Seed method. Listing 15-8 shows how I added a Country property to the AppUser class. 现在,已经建立了数据库迁移,我打算再定义一个属性,这恰恰演示了如何处理持续不断的修改,也为了演示Configuration.Seed方法更有用(至少无害)的示例。清单15-8显示了我在AppUser类上添加了一个Country属性。 Listing 15-8. Adding Another Property in the AppUserModels.cs File 清单15-8. 在AppUserModels.cs文件中添加另一个属性 using System;using Microsoft.AspNet.Identity.EntityFramework; namespace Users.Models {public enum Cities {LONDON, PARIS, CHICAGO} public enum Countries {NONE, UK, FRANCE, USA}public class AppUser : IdentityUser {public Cities City { get; set; }public Countries Country { get; set; }public void SetCountryFromCity(Cities city) {switch (city) {case Cities.LONDON:Country = Countries.UK;break;case Cities.PARIS:Country = Countries.FRANCE;break;case Cities.CHICAGO:Country = Countries.USA;break;default:Country = Countries.NONE;break;} }} } I have added an enumeration to define the country names and a helper method that selects a country value based on the City property. Listing 15-9 shows the change I made to the Configuration class so that the Seed method sets the Country property based on the City, but only if the value of Country is NONE (which it will be for all users when the database is migrated because the Entity Framework sets enumeration columns to the first value). 我已经添加了一个枚举,它定义了国家名称。还添加了一个辅助器方法,它可以根据City属性选择一个国家。清单15-9显示了对Configuration类所做的修改,以使Seed方法根据City设置Country属性,但只当Country为NONE时才进行设置(在迁移数据库时,所有用户都是NONE,因为Entity Framework会将枚举列设置为枚举的第一个值)。 Listing 15-9. Modifying the Database Seed in the Configuration.cs File 清单15-9. 在Configuration.cs文件中修改数据库种子 using System.Data.Entity.Migrations;using Microsoft.AspNet.Identity;using Microsoft.AspNet.Identity.EntityFramework;using Users.Infrastructure;using Users.Models; namespace Users.Migrations {internal sealed class Configuration: DbMigrationsConfiguration<AppIdentityDbContext> {public Configuration() {AutomaticMigrationsEnabled = true;ContextKey = "Users.Infrastructure.AppIdentityDbContext";}protected override void Seed(AppIdentityDbContext context) {AppUserManager userMgr = new AppUserManager(new UserStore<AppUser>(context));AppRoleManager roleMgr = new AppRoleManager(new RoleStore<AppRole>(context)); string roleName = "Administrators";string userName = "Admin";string password = "MySecret";string email = "admin@example.com";if (!roleMgr.RoleExists(roleName)) {roleMgr.Create(new AppRole(roleName));}AppUser user = userMgr.FindByName(userName);if (user == null) {userMgr.Create(new AppUser { UserName = userName, Email = email },password);user = userMgr.FindByName(userName);}if (!userMgr.IsInRole(user.Id, roleName)) {userMgr.AddToRole(user.Id, roleName);} foreach (AppUser dbUser in userMgr.Users) {if (dbUser.Country == Countries.NONE) {dbUser.SetCountryFromCity(dbUser.City);} }context.SaveChanges();} }} This kind of seeding is more useful in a real project because it will set a value for the Country property only if one has not already been set—subsequent migrations won’t be affected, and user selections won’t be lost. 这种种植在实际项目中会更有用,因为它只会在Country属性未设置时,才会设置Country属性的值——后继的迁移不会受到影响,因此不会失去用户的选择。 1. Adding Application Support 1. 添加应用程序支持 There is no point defining additional user properties if they are not available in the application, so Listing 15-10 shows the change I made to the Views/Home/UserProps.cshtml file to display the value of the Country property. 应用程序中如果没有定义附加属性的地方,则附加属性就无法使用了,因此,清单15-10显示了我对Views/Home/UserProps.cshtml文件的修改,以显示Country属性的值。 Listing 15-10. Displaying an Additional Property in the UserProps.cshtml File 清单15-10. 在UserProps.cshtml文件中显示附加属性 @using Users.Models@model AppUser@{ ViewBag.Title = "UserProps";} <div class="panel panel-primary"><div class="panel-heading">Custom User Properties</div><table class="table table-striped"><tr><th>City</th><td>@Model.City</td></tr> <tr><th>Country</th><td>@Model.Country</td></tr></table></div>@using (Html.BeginForm()) {<div class="form-group"><label>City</label>@Html.DropDownListFor(x => x.City, new SelectList(Enum.GetNames(typeof(Cities))))</div><button class="btn btn-primary" type="submit">Save</button>} Listing 15-11 shows the corresponding change I made to the Home controller to update the Country property when the City value changes. 为了在City值变化时能够更新Country属性,清单15-11显示了我对Home控制器所做的相应修改。 Listing 15-11. Setting Custom Properties in the HomeController.cs File 清单15-11. 在HomeController.cs文件中设置自定义属性 using System.Web.Mvc;using System.Collections.Generic;using System.Web;using System.Security.Principal;using System.Threading.Tasks;using Users.Infrastructure;using Microsoft.AspNet.Identity;using Microsoft.AspNet.Identity.Owin;using Users.Models; namespace Users.Controllers {public class HomeController : Controller {// ...other action methods omitted for brevity...// ...出于简化,这里忽略了其他动作方法... [Authorize]public ActionResult UserProps() {return View(CurrentUser);}[Authorize][HttpPost]public async Task<ActionResult> UserProps(Cities city) {AppUser user = CurrentUser;user.City = city;user.SetCountryFromCity(city);await UserManager.UpdateAsync(user);return View(user);}// ...properties omitted for brevity...// ...出于简化,这里忽略了一些属性...} } 2. Performing the Migration 2. 准备迁移 All that remains is to create and apply a new migration. Enter the following command into the Package Manager Console: 剩下的事情就是创建和运用新的迁移了。在“Package Manager Console(包管理器控制台)”中输入以下命令: Add-Migration CountryProperty This will generate another file in the Migrations folder that contains the instruction to add the Country column. To apply the migration, execute the following command: 这将在Migrations文件夹中生成另一个文件,它含有添加Country数据表列的指令。为了运用迁移,可执行以下命令: Update-Database –TargetMigration CountryProperty The migration will be performed, and the value of the Country property will be set based on the value of the existing City property for each user. You can check the new user property by starting the application and authenticating and navigating to the /Home/UserProps URL, as shown in Figure 15-4. 这将执行迁移,Country属性的值将根据每个用户当前的City属性进行设置。通过启动应用程序,认证并导航到/Home/UserProps URL,便可以查看新的用户属性,如图15-4所示。 Figure 15-4. Creating an additional user property 图15-4. 创建附加用户属性 Tip Although I am focused on the process of upgrading the database, you can also migrate back to a previous version by specifying an earlier migration. Use the –Force argument make changes that cause data loss, such as removing a column. 提示:虽然我们关注了升级数据库的过程,但你也可以回退到以前的版本,只需指定一个早期的迁移即可。使用-Force参数进行修改,会引起数据丢失,例如删除数据表列。 15.3 Working with Claims 15.3 使用声明(Claims) In older user-management systems, such as ASP.NET Membership, the application was assumed to be the authoritative source of all information about the user, essentially treating the application as a closed world and trusting the data that is contained within it. 在旧的用户管理系统中,例如ASP.NET Membership,应用程序被假设成是用户所有信息的权威来源,本质上将应用程序视为是一个封闭的世界,并且只信任其中所包含的数据。 This is such an ingrained approach to software development that it can be hard to recognize that’s what is happening, but you saw an example of the closed-world technique in Chapter 14 when I authenticated users against the credentials stored in the database and granted access based on the roles associated with those credentials. I did the same thing again in this chapter when I added properties to the user class. Every piece of information that I needed to manage user authentication and authorization came from within my application—and that is a perfectly satisfactory approach for many web applications, which is why I demonstrated these techniques in such depth. 这是软件开发的一种根深蒂固的方法,使人很难认识到这到底意味着什么,第14章你已看到了这种封闭世界技术的例子,根据存储在数据库中的凭据来认证用户,并根据与凭据关联在一起的角色来授权访问。本章前述在用户类上添加属性,也做了同样的事情。我管理用户认证与授权所需的每一个数据片段都来自于我的应用程序——而且这是许多Web应用程序都相当满意的一种方法,这也是我如此深入地演示这些技术的原因。 ASP.NET Identity also supports an alternative approach for dealing with users, which works well when the MVC framework application isn’t the sole source of information about users and which can be used to authorize users in more flexible and fluid ways than traditional roles allow. ASP.NET Identity还支持另一种处理用户的办法,当MVC框架的应用程序不是有关用户的唯一信息源时,这种办法会工作得很好,而且能够比传统的角色授权更为灵活且流畅的方式进行授权。 This alternative approach uses claims, and in this section I’ll describe how ASP.NET Identity supports claims-based authorization. Table 15-4 puts claims in context. 这种可选的办法使用了“Claims(声明)”,因此在本小节中,我将描述ASP.NET Identity如何支持“Claims-Based Authorization(基于声明的授权)”。表15-4描述了声明(Claims)的情形。 提示:“Claim”在英文字典中不完全是“声明”的意思,根据本文的描述,感觉把它说成“声明”也不一定合适,所以在之后的译文中基本都写成中英文并用的形式,即“声明(Claims)”。根据表15-4中的声明(Claims)的定义:声明(Claims)是关于用户的一些信息片段。一个用户的信息片段当然有很多,每一个信息片段就是一项声明(Claim),用户的所有信息片段合起来就是该用户的声明(Claims)。请读者注意该单词的单复数形式——译者注 Table 15-4. Putting Claims in Context 表15-4. 声明(Claims)的情形 Question 问题 Answer 答案 What is it? 什么是声明(Claims)? Claims are pieces of information about users that you can use to make authorization decisions. Claims can be obtained from external systems as well as from the local Identity database. 声明(Claims)是关于用户的一些信息片段,可以用它们做出授权决定。声明(Claims)可以从外部系统获取,也可以从本地的Identity数据库获取。 Why should I care? 为何要关心它? Claims can be used to flexibly authorize access to action methods. Unlike conventional roles, claims allow access to be driven by the information that describes the user. 声明(Claims)可以用来对动作方法进行灵活的授权访问。与传统的角色不同,声明(Claims)让访问能够由描述用户的信息进行驱动。 How is it used by the MVC framework? 如何在MVC框架中使用它? This feature isn’t used directly by the MVC framework, but it is integrated into the standard authorization features, such as the Authorize attribute. 这不是直接由MVC框架使用的特性,但它集成到了标准的授权特性之中,例如Authorize注解属性。 Tip you don’t have to use claims in your applications, and as Chapter 14 showed, ASP.NET Identity is perfectly happy providing an application with the authentication and authorization services without any need to understand claims at all. 提示:你在应用程序中不一定要使用声明(Claims),正如第14章所展示的那样,ASP.NET Identity能够为应用程序提供充分的认证与授权服务,而根本不需要理解声明(Claims)。 15.3.1 Understanding Claims 15.3.1 理解声明(Claims) A claim is a piece of information about the user, along with some information about where the information came from. The easiest way to unpack claims is through some practical demonstrations, without which any discussion becomes too abstract to be truly useful. To get started, I added a Claims controller to the example project, the definition of which you can see in Listing 15-12. 一项声明(Claim)是关于用户的一个信息片段(请注意这个英文单词的单复数形式——译者注),并伴有该片段出自何处的某种信息。揭开声明(Claims)含义最容易的方式是做一些实际演示,任何讨论都会过于抽象根本没有真正的用处。为此,我在示例项目中添加了一个Claims控制器,其定义如清单15-12所示。 Listing 15-12. The Contents of the ClaimsController.cs File 清单15-12. ClaimsController.cs文件的内容 using System.Security.Claims;using System.Web;using System.Web.Mvc; namespace Users.Controllers {public class ClaimsController : Controller {[Authorize]public ActionResult Index() {ClaimsIdentity ident = HttpContext.User.Identity as ClaimsIdentity;if (ident == null) {return View("Error", new string[] { "No claims available" });} else {return View(ident.Claims);} }} } Tip You may feel a little lost as I define the code for this example. Don’t worry about the details for the moment—just stick with it until you see the output from the action method and view that I define. More than anything else, that will help put claims into perspective. 提示:你或许会对我为此例定义的代码感到有点失望。此刻对此细节不必着急——只要稍事忍耐,当看到该动作方法和视图的输出便会明白。尤为重要的是,这有助于洞察声明(Claims)。 You can get the claims associated with a user in different ways. One approach is to use the Claims property defined by the user class, but in this example, I have used the HttpContext.User.Identity property to demonstrate the way that ASP.NET Identity is integrated with the rest of the ASP.NET platform. As I explained in Chapter 13, the HttpContext.User.Identity property returns an implementation of the IIdentity interface, which is a ClaimsIdentity object when working using ASP.NET Identity. The ClaimsIdentity class is defined in the System.Security.Claims namespace, and Table 15-5 shows the members it defines that are relevant to this chapter. 可以通过不同的方式获得与用户相关联的声明(Claims)。方法之一就是使用由用户类定义的Claims属性,但在这个例子中,我使用了HttpContext.User.Identity属性,目的是演示ASP.NET Identity与ASP.NET平台集成的方式(请注意这句话所表示的含义:用户类的Claims属性属于ASP.NET Identity,而HttpContext.User.Identity属性则属于ASP.NET平台。由此可见,ASP.NET Identity已经融合到了ASP.NET平台之中——译者注)。正如第13章所解释的那样,HttpContext.User.Identity属性返回IIdentity的接口实现,当使用ASP.NET Identity时,该实现是一个ClaimsIdentity对象。ClaimsIdentity类是在System.Security.Claims命名空间中定义的,表15-5显示了它所定义的与本章有关的成员。 Table 15-5. The Members Defined by the ClaimsIdentity Class 表15-5. ClaimsIdentity类所定义的成员 Name 名称 Description 描述 Claims Returns an enumeration of Claim objects representing the claims for the user. 返回表示用户声明(Claims)的Claim对象枚举 AddClaim(claim) Adds a claim to the user identity. 给用户添加一个声明(Claim) AddClaims(claims) Adds an enumeration of Claim objects to the user identity. 给用户添加Claim对象的枚举。 HasClaim(predicate) Returns true if the user identity contains a claim that matches the specified predicate. See the “Applying Claims” section for an example predicate. 如果用户含有与指定谓词匹配的声明(Claim)时,返回true。参见“运用声明(Claims)”中的示例谓词 RemoveClaim(claim) Removes a claim from the user identity. 删除用户的声明(Claim)。 Other members are available, but the ones in the table are those that are used most often in web applications, for reason that will become obvious as I demonstrate how claims fit into the wider ASP.NET platform. 还有一些可用的其它成员,但表中的这些是在Web应用程序中最常用的,随着我演示如何将声明(Claims)融入更宽泛的ASP.NET平台,它们为什么最常用就很显然了。 In Listing 15-12, I cast the IIdentity implementation to the ClaimsIdentity type and pass the enumeration of Claim objects returned by the ClaimsIdentity.Claims property to the View method. A Claim object represents a single piece of data about the user, and the Claim class defines the properties shown in Table 15-6. 在清单15-12中,我将IIdentity实现转换成了ClaimsIdentity类型,并且给View方法传递了ClaimsIdentity.Claims属性所返回的Claim对象的枚举。Claim对象所示表示的是关于用户的一个单一的数据片段,Claim类定义的属性如表15-6所示。 Table 15-6. The Properties Defined by the Claim Class 表15-6. Claim类定义的属性 Name 名称 Description 描述 Issuer Returns the name of the system that provided the claim 返回提供声明(Claim)的系统名称 Subject Returns the ClaimsIdentity object for the user who the claim refers to 返回声明(Claim)所指用户的ClaimsIdentity对象 Type Returns the type of information that the claim represents 返回声明(Claim)所表示的信息类型 Value Returns the piece of information that the claim represents 返回声明(Claim)所表示的信息片段 Listing 15-13 shows the contents of the Index.cshtml file that I created in the Views/Claims folder and that is rendered by the Index action of the Claims controller. The view adds a row to a table for each claim about the user. 清单15-13显示了我在Views/Claims文件夹中创建的Index.cshtml文件的内容,它由Claims控制器中的Index动作方法进行渲染。该视图为用户的每项声明(Claim)添加了一个表格行。 Listing 15-13. The Contents of the Index.cshtml File in the Views/Claims Folder 清单15-13. Views/Claims文件夹中Index.cshtml文件的内容 @using System.Security.Claims@using Users.Infrastructure@model IEnumerable<Claim>@{ ViewBag.Title = "Claims"; }<div class="panel panel-primary"><div class="panel-heading">Claims</div><table class="table table-striped"><tr><th>Subject</th><th>Issuer</th><th>Type</th><th>Value</th></tr>@foreach (Claim claim in Model.OrderBy(x => x.Type)) {<tr><td>@claim.Subject.Name</td><td>@claim.Issuer</td><td>@Html.ClaimType(claim.Type)</td><td>@claim.Value</td></tr>}</table></div> The value of the Claim.Type property is a URI for a Microsoft schema, which isn’t especially useful. The popular schemas are used as the values for fields in the System.Security.Claims.ClaimTypes class, so to make the output from the Index.cshtml view easier to read, I added an HTML helper to the IdentityHelpers.cs file, as shown in Listing 15-14. It is this helper that I use in the Index.cshtml file to format the value of the Claim.Type property. Claim.Type属性的值是一个微软模式(Microsoft Schema)的URI(统一资源标识符),这是特别有用的。System.Security.Claims.ClaimTypes类中字段的值使用的是流行模式(Popular Schema),因此为了使Index.cshtml视图的输出更易于阅读,我在IdentityHelpers.cs文件中添加了一个HTML辅助器,如清单15-14所示。Index.cshtml文件正是使用这个辅助器格式化了Claim.Type属性的值。 Listing 15-14. Adding a Helper to the IdentityHelpers.cs File 清单15-14. 在IdentityHelpers.cs文件中添加辅助器 using System.Web;using System.Web.Mvc;using Microsoft.AspNet.Identity.Owin;using System;using System.Linq;using System.Reflection;using System.Security.Claims;namespace Users.Infrastructure {public static class IdentityHelpers {public static MvcHtmlString GetUserName(this HtmlHelper html, string id) {AppUserManager mgr= HttpContext.Current.GetOwinContext().GetUserManager<AppUserManager>();return new MvcHtmlString(mgr.FindByIdAsync(id).Result.UserName);} public static MvcHtmlString ClaimType(this HtmlHelper html, string claimType) {FieldInfo[] fields = typeof(ClaimTypes).GetFields();foreach (FieldInfo field in fields) {if (field.GetValue(null).ToString() == claimType) {return new MvcHtmlString(field.Name);} }return new MvcHtmlString(string.Format("{0}",claimType.Split('/', '.').Last()));} }} Note The helper method isn’t at all efficient because it reflects on the fields of the ClaimType class for each claim that is displayed, but it is sufficient for my purposes in this chapter. You won’t often need to display the claim type in real applications. 注:该辅助器并非十分有效,因为它只是针对每个要显示的声明(Claim)映射出ClaimType类的字段,但对我要的目的已经足够了。在实际项目中不会经常需要显示声明(Claim)的类型。 To see why I have created a controller that uses claims without really explaining what they are, start the application, authenticate as the user Alice (with the password MySecret), and request the /Claims/Index URL. Figure 15-5 shows the content that is generated. 为了弄明白我为何要先创建一个使用声明(Claims)的控制器,而没有真正解释声明(Claims)是什么的原因,可以启动应用程序,以用户Alice进行认证(其口令是MySecret),并请求/Claims/Index URL。图15-5显示了生成的内容。 Figure 15-5. The output from the Index action of the Claims controller 图15-5. Claims控制器中Index动作的输出 It can be hard to make out the detail in the figure, so I have reproduced the content in Table 15-7. 这可能还难以认识到此图的细节,为此我在表15-7中重列了其内容。 Table 15-7. The Data Shown in Figure 15-5 表15-7. 图15-5中显示的数据 Subject(科目) Issuer(发行者) Type(类型) Value(值) Alice LOCAL AUTHORITY SecurityStamp Unique ID Alice LOCAL AUTHORITY IdentityProvider ASP.NET Identity Alice LOCAL AUTHORITY Role Employees Alice LOCAL AUTHORITY Role Users Alice LOCAL AUTHORITY Name Alice Alice LOCAL AUTHORITY NameIdentifier Alice’s user ID The table shows the most important aspect of claims, which is that I have already been using them when I implemented the traditional authentication and authorization features in Chapter 14. You can see that some of the claims relate to user identity (the Name claim is Alice, and the NameIdentifier claim is Alice’s unique user ID in my ASP.NET Identity database). 此表展示了声明(Claims)最重要的方面,这些是我在第14章中实现传统的认证和授权特性时,一直在使用的信息。可以看出,有些声明(Claims)与用户标识有关(Name声明是Alice,NameIdentifier声明是Alice在ASP.NET Identity数据库中的唯一用户ID号)。 Other claims show membership of roles—there are two Role claims in the table, reflecting the fact that Alice is assigned to both the Users and Employees roles. There is also a claim about how Alice has been authenticated: The IdentityProvider is set to ASP.NET Identity. 其他声明(Claims)显示了角色成员——表中有两个Role声明(Claim),体现出Alice被赋予了Users和Employees两个角色这一事实。还有一个是Alice已被认证的声明(Claim):IdentityProvider被设置到了ASP.NET Identity。 The difference when this information is expressed as a set of claims is that you can determine where the data came from. The Issuer property for all the claims shown in the table is set to LOCAL AUTHORITY, which indicates that the user’s identity has been established by the application. 当这种信息被表示成一组声明(Claims)时的差别是,你能够确定这些数据是从哪里来的。表中所显示的所有声明的Issuer属性(发布者)都被设置到了LOACL AUTHORITY(本地授权),这说明该用户的标识是由应用程序建立的。 So, now that you have seen some example claims, I can more easily describe what a claim is. A claim is any piece of information about a user that is available to the application, including the user’s identity and role memberships. And, as you have seen, the information I have been defining about my users in earlier chapters is automatically made available as claims by ASP.NET Identity. 因此,现在你已经看到了一些声明(Claims)示例,我可以更容易地描述声明(Claim)是什么了。一项声明(Claim)是可用于应用程序中的有关用户的一个信息片段,包括用户的标识以及角色成员等。而且,正如你所看到的,我在前几章定义的关于用户的信息,被ASP.NET Identity自动地作为声明(Claims)了。 15.3.2 Creating and Using Claims 15.3.2 创建和使用声明(Claims) Claims are interesting for two reasons. The first reason is that an application can obtain claims from multiple sources, rather than just relying on a local database for information about the user. You will see a real example of this when I show you how to authenticate users through a third-party system in the “Using Third-Party Authentication” section, but for the moment I am going to add a class to the example project that simulates a system that provides claims information. Listing 15-15 shows the contents of the LocationClaimsProvider.cs file that I added to the Infrastructure folder. 声明(Claims)比较有意思的原因有两个。第一个原因是应用程序可以从多个来源获取声明(Claims),而不是只能依靠本地数据库关于用户的信息。你将会看到一个实际的示例,在“使用第三方认证”小节中,将演示如何通过第三方系统来认证用户。不过,此刻我只打算在示例项目中添加一个类,用以模拟一个提供声明(Claims)信息的系统。清单15-15显示了我添加到Infrastructure文件夹中LocationClaimsProvider.cs文件的内容。 Listing 15-15. The Contents of the LocationClaimsProvider.cs File 清单15-15. LocationClaimsProvider.cs文件的内容 using System.Collections.Generic;using System.Security.Claims; namespace Users.Infrastructure {public static class LocationClaimsProvider {public static IEnumerable<Claim> GetClaims(ClaimsIdentity user) {List<Claim> claims = new List<Claim>();if (user.Name.ToLower() == "alice") {claims.Add(CreateClaim(ClaimTypes.PostalCode, "DC 20500"));claims.Add(CreateClaim(ClaimTypes.StateOrProvince, "DC"));} else {claims.Add(CreateClaim(ClaimTypes.PostalCode, "NY 10036"));claims.Add(CreateClaim(ClaimTypes.StateOrProvince, "NY"));}return claims;}private static Claim CreateClaim(string type, string value) {return new Claim(type, value, ClaimValueTypes.String, "RemoteClaims");} }} The GetClaims method takes a ClaimsIdentity argument and uses the Name property to create claims about the user’s ZIP code and state. This class allows me to simulate a system such as a central HR database, which would be the authoritative source of location information about staff, for example. GetClaims方法以ClaimsIdentity为参数,并使用Name属性创建了关于用户ZIP码(邮政编码)和州府的声明(Claims)。上述这个类使我能够模拟一个诸如中心化的HR数据库(人力资源数据库)之类的系统,它可能会成为全体职员的地点信息的权威数据源。 Claims are associated with the user’s identity during the authentication process, and Listing 15-16 shows the changes I made to the Login action method of the Account controller to call the LocationClaimsProvider class. 在认证过程期间,声明(Claims)是与用户标识关联在一起的,清单15-16显示了我对Account控制器中Login动作方法所做的修改,以便调用LocationClaimsProvider类。 Listing 15-16. Associating Claims with a User in the AccountController.cs File 清单15-16. AccountController.cs文件中用户用声明的关联 ...[HttpPost][AllowAnonymous][ValidateAntiForgeryToken]public async Task<ActionResult> Login(LoginModel details, string returnUrl) {if (ModelState.IsValid) {AppUser user = await UserManager.FindAsync(details.Name,details.Password);if (user == null) {ModelState.AddModelError("", "Invalid name or password.");} else {ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user,DefaultAuthenticationTypes.ApplicationCookie); ident.AddClaims(LocationClaimsProvider.GetClaims(ident));AuthManager.SignOut();AuthManager.SignIn(new AuthenticationProperties {IsPersistent = false}, ident);return Redirect(returnUrl);} }ViewBag.returnUrl = returnUrl;return View(details);}... You can see the effect of the location claims by starting the application, authenticating as a user, and requesting the /Claim/Index URL. Figure 15-6 shows the claims for Alice. You may have to sign out and sign back in again to see the change. 为了看看这个地点声明(Claims)的效果,可以启动应用程序,以一个用户进行认证,并请求/Claim/Index URL。图15-6显示了Alice的声明(Claims)。你可能需要退出,然后再次登录才会看到发生的变化。 Figure 15-6. Defining additional claims for users 图15-6. 定义用户的附加声明 Obtaining claims from multiple locations means that the application doesn’t have to duplicate data that is held elsewhere and allows integration of data from external parties. The Claim.Issuer property tells you where a claim originated from, which helps you judge how accurate the data is likely to be and how much weight you should give the data in your application. Location data obtained from a central HR database is likely to be more accurate and trustworthy than data obtained from an external mailing list provider, for example. 从多个地点获取声明(Claims)意味着应用程序不必复制其他地方保持的数据,并且能够与外部的数据集成。Claim.Issuer属性(图15-6中的Issuer数据列——译者注)能够告诉你一个声明(Claim)的发源地,这有助于让你判断数据的精确程度,也有助于让你决定这类数据在应用程序中的权重。例如,从中心化的HR数据库获取的地点数据可能要比外部邮件列表提供器获取的数据更为精确和可信。 1. Applying Claims 1. 运用声明(Claims) The second reason that claims are interesting is that you can use them to manage user access to your application more flexibly than with standard roles. The problem with roles is that they are static, and once a user has been assigned to a role, the user remains a member until explicitly removed. This is, for example, how long-term employees of big corporations end up with incredible access to internal systems: They are assigned the roles they require for each new job they get, but the old roles are rarely removed. (The unexpectedly broad systems access sometimes becomes apparent during the investigation into how someone was able to ship the contents of the warehouse to their home address—true story.) 声明(Claims)有意思的第二个原因是,你可以用它们来管理用户对应用程序的访问,这要比标准的角色管理更为灵活。角色的问题在于它们是静态的,而且一旦用户已经被赋予了一个角色,该用户便是一个成员,直到明确地删除为止。例如,这意味着大公司的长期雇员,对内部系统的访问会十分惊人:他们每次在获得新工作时,都会赋予所需的角色,但旧角色很少被删除。(在调查某人为何能够将仓库里的东西发往他的家庭地址过程中发现,有时会出现异常宽泛的系统访问——真实的故事) Claims can be used to authorize users based directly on the information that is known about them, which ensures that the authorization changes when the data changes. The simplest way to do this is to generate Role claims based on user data that are then used by controllers to restrict access to action methods. Listing 15-17 shows the contents of the ClaimsRoles.cs file that I added to the Infrastructure. 声明(Claims)可以直接根据用户已知的信息对用户进行授权,这能够保证当数据发生变化时,授权也随之而变。此事最简单的做法是根据用户数据来生成Role声明(Claim),然后由控制器用来限制对动作方法的访问。清单15-17显示了我添加到Infrastructure中的ClaimsRoles.cs文件的内容。 Listing 15-17. The Contents of the ClaimsRoles.cs File 清单15-17. ClaimsRoles.cs文件的内容 using System.Collections.Generic;using System.Security.Claims; namespace Users.Infrastructure {public class ClaimsRoles {public static IEnumerable<Claim> CreateRolesFromClaims(ClaimsIdentity user) {List<Claim> claims = new List<Claim>();if (user.HasClaim(x => x.Type == ClaimTypes.StateOrProvince&& x.Issuer == "RemoteClaims" && x.Value == "DC")&& user.HasClaim(x => x.Type == ClaimTypes.Role&& x.Value == "Employees")) {claims.Add(new Claim(ClaimTypes.Role, "DCStaff"));}return claims;} }} The gnarly looking CreateRolesFromClaims method uses lambda expressions to determine whether the user has a StateOrProvince claim from the RemoteClaims issuer with a value of DC and a Role claim with a value of Employees. If the user has both claims, then a Role claim is returned for the DCStaff role. Listing 15-18 shows how I call the CreateRolesFromClaims method from the Login action in the Account controller. CreateRolesFromClaims是一个粗糙的考察方法,它使用了Lambda表达式,以检查用户是否具有StateOrProvince声明(Claim),该声明来自于RemoteClaims发行者(Issuer),值为DC。也检查用户是否具有Role声明(Claim),其值为Employees。如果用户这两个声明都有,那么便返回一个DCStaff角色的Role声明。清单15-18显示了如何在Account控制器中的Login动作中调用CreateRolesFromClaims方法。 Listing 15-18. Generating Roles Based on Claims in the AccountController.cs File 清单15-18. 在AccountController.cs中根据声明生成角色 ...[HttpPost][AllowAnonymous][ValidateAntiForgeryToken]public async Task<ActionResult> Login(LoginModel details, string returnUrl) {if (ModelState.IsValid) {AppUser user = await UserManager.FindAsync(details.Name,details.Password);if (user == null) {ModelState.AddModelError("", "Invalid name or password.");} else {ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user,DefaultAuthenticationTypes.ApplicationCookie);ident.AddClaims(LocationClaimsProvider.GetClaims(ident)); ident.AddClaims(ClaimsRoles.CreateRolesFromClaims(ident));AuthManager.SignOut();AuthManager.SignIn(new AuthenticationProperties {IsPersistent = false}, ident);return Redirect(returnUrl);} }ViewBag.returnUrl = returnUrl;return View(details);}... I can then restrict access to an action method based on membership of the DCStaff role. Listing 15-19 shows a new action method I added to the Claims controller to which I have applied the Authorize attribute. 然后我可以根据DCStaff角色的成员,来限制对一个动作方法的访问。清单15-19显示了在Claims控制器中添加的一个新的动作方法,在该方法上已经运用了Authorize注解属性。 Listing 15-19. Adding a New Action Method to the ClaimsController.cs File 清单15-19. 在ClaimsController.cs文件中添加一个新的动作方法 using System.Security.Claims;using System.Web;using System.Web.Mvc;namespace Users.Controllers {public class ClaimsController : Controller {[Authorize]public ActionResult Index() {ClaimsIdentity ident = HttpContext.User.Identity as ClaimsIdentity;if (ident == null) {return View("Error", new string[] { "No claims available" });} else {return View(ident.Claims);} } [Authorize(Roles="DCStaff")]public string OtherAction() {return "This is the protected action";} }} Users will be able to access OtherAction only if their claims grant them membership to the DCStaff role. Membership of this role is generated dynamically, so a change to the user’s employment status or location information will change their authorization level. 只要用户的声明(Claims)承认他们是DCStaff角色的成员,那么他们便能访问OtherAction动作。该角色的成员是动态生成的,因此,若是用户的雇用状态或地点信息发生变化,也会改变他们的授权等级。 提示:请读者从这个例子中吸取其中的思想精髓。对于读物的理解程度,仁者见仁,智者见智,能领悟多少,全凭各人,译者感觉这里的思想有无数的可能。举例说明:(1)可以根据用户的身份进行授权,比如学生在校时是“学生”,毕业后便是“校友”;(2)可以根据用户所处的部门进行授权,人事部用户属于人事团队,销售部用户属于销售团队,各团队有其自己的应用;(3)下一小节的示例是根据用户的地点授权。简言之:一方面用户的各种声明(Claim)都可以用来进行授权;另一方面用户的声明(Claim)又是可以自定义的。于是可能的运用就无法估计了。总之一句话,这种基于声明的授权(Claims-Based Authorization)有无限可能!要是没有我这里的提示,是否所有读者在此处都会有所体会?——译者注 15.3.3 Authorizing Access Using Claims 15.3.3 使用声明(Claims)授权访问 The previous example is an effective demonstration of how claims can be used to keep authorizations fresh and accurate, but it is a little indirect because I generate roles based on claims data and then enforce my authorization policy based on the membership of that role. A more direct and flexible approach is to enforce authorization directly by creating a custom authorization filter attribute. Listing 15-20 shows the contents of the ClaimsAccessAttribute.cs file, which I added to the Infrastructure folder and used to create such a filter. 前面的示例有效地演示了如何用声明(Claims)来保持新鲜和准确的授权,但有点不太直接,因为我要根据声明(Claims)数据来生成了角色,然后强制我的授权策略基于角色成员。一个更直接且灵活的办法是直接强制授权,其做法是创建一个自定义的授权过滤器注解属性。清单15-20演示了ClaimsAccessAttribute.cs文件的内容,我将它添加在Infrastructure文件夹中,并用它创建了这种过滤器。 Listing 15-20. The Contents of the ClaimsAccessAttribute.cs File 清单15-20. ClaimsAccessAttribute.cs文件的内容 using System.Security.Claims;using System.Web;using System.Web.Mvc; namespace Users.Infrastructure {public class ClaimsAccessAttribute : AuthorizeAttribute {public string Issuer { get; set; }public string ClaimType { get; set; }public string Value { get; set; }protected override bool AuthorizeCore(HttpContextBase context) {return context.User.Identity.IsAuthenticated&& context.User.Identity is ClaimsIdentity&& ((ClaimsIdentity)context.User.Identity).HasClaim(x =>x.Issuer == Issuer && x.Type == ClaimType && x.Value == Value);} }} The attribute I have defined is derived from the AuthorizeAttribute class, which makes it easy to create custom authorization policies in MVC framework applications by overriding the AuthorizeCore method. My implementation grants access if the user is authenticated, the IIdentity implementation is an instance of ClaimsIdentity, and the user has a claim with the issuer, type, and value matching the class properties. Listing 15-21 shows how I applied the attribute to the Claims controller to authorize access to the OtherAction method based on one of the location claims created by the LocationClaimsProvider class. 我所定义的这个注解属性派生于AuthorizeAttribute类,通过重写AuthorizeCore方法,很容易在MVC框架应用程序中创建自定义的授权策略。在这个实现中,若用户是已认证的、其IIdentity实现是一个ClaimsIdentity实例,而且该用户有一个带有issuer、type以及value的声明(Claim),它们与这个类的属性是匹配的,则该用户便是允许访问的。清单15-21显示了如何将这个注解属性运用于Claims控制器,以便根据LocationClaimsProvider类创建的地点声明(Claim),对OtherAction方法进行授权访问。 Listing 15-21. Performing Authorization on Claims in the ClaimsController.cs File 清单15-21. 在ClaimsController.cs文件中执行基于声明的授权 using System.Security.Claims;using System.Web;using System.Web.Mvc;using Users.Infrastructure;namespace Users.Controllers {public class ClaimsController : Controller {[Authorize]public ActionResult Index() {ClaimsIdentity ident = HttpContext.User.Identity as ClaimsIdentity;if (ident == null) {return View("Error", new string[] { "No claims available" });} else {return View(ident.Claims);} } [ClaimsAccess(Issuer="RemoteClaims", ClaimType=ClaimTypes.PostalCode,Value="DC 20500")]public string OtherAction() {return "This is the protected action";} }} My authorization filter ensures that only users whose location claims specify a ZIP code of DC 20500 can invoke the OtherAction method. 这个授权过滤器能够确保只有地点声明(Claim)的邮编为DC 20500的用户才能请求OtherAction方法。 15.4 Using Third-Party Authentication 15.4 使用第三方认证 One of the benefits of a claims-based system such as ASP.NET Identity is that any of the claims can come from an external system, even those that identify the user to the application. This means that other systems can authenticate users on behalf of the application, and ASP.NET Identity builds on this idea to make it simple and easy to add support for authenticating users through third parties such as Microsoft, Google, Facebook, and Twitter. 基于声明的系统,如ASP.NET Identity,的好处之一是任何声明都可以来自于外部系统,即使是将用户标识到应用程序的那些声明。这意味着其他系统可以代表应用程序来认证用户,而ASP.NET Identity就建立在这样的思想之上,使之能够简单而方便地添加第三方认证用户的支持,如微软、Google、Facebook、Twitter等。 There are some substantial benefits of using third-party authentication: Many users will already have an account, users can elect to use two-factor authentication, and you don’t have to manage user credentials in the application. In the sections that follow, I’ll show you how to set up and use third-party authentication for Google users, which Table 15-8 puts into context. 使用第三方认证有一些实际的好处:许多用户已经有了账号、用户可以选择使用双因子认证、你不必在应用程序中管理用户凭据等等。在以下小节中,我将演示如何为Google用户建立并使用第三方认证,表15-8描述了事情的情形。 Table 15-8. Putting Third-Party Authentication in Context 表15-8. 第三方认证情形 Question 问题 Answer 回答 What is it? 什么是第三方认证? Authenticating with third parties lets you take advantage of the popularity of companies such as Google and Facebook. 第三方认证使你能够利用流行公司,如Google和Facebook,的优势。 Why should I care? 为何要关心它? Users don’t like having to remember passwords for many different sites. Using a provider with large-scale adoption can make your application more appealing to users of the provider’s services. 用户不喜欢记住许多不同网站的口令。使用大范围适应的提供器可使你的应用程序更吸引有提供器服务的用户。 How is it used by the MVC framework? 如何在MVC框架中使用它? This feature isn’t used directly by the MVC framework. 这不是一个直接由MVC框架使用的特性。 Note The reason I have chosen to demonstrate Google authentication is that it is the only option that doesn’t require me to register my application with the authentication service. You can get details of the registration processes required at http://bit.ly/1cqLTrE. 提示:我选择演示Google认证的原因是,它是唯一不需要在其认证服务中注册我应用程序的公司。有关认证服务注册过程的细节,请参阅http://bit.ly/1cqLTrE。 15.4.1 Enabling Google Authentication 15.4.1 启用Google认证 ASP.NET Identity comes with built-in support for authenticating users through their Microsoft, Google, Facebook, and Twitter accounts as well more general support for any authentication service that supports OAuth. The first step is to add the NuGet package that includes the Google-specific additions for ASP.NET Identity. Enter the following command into the Package Manager Console: ASP.NET Identity带有通过Microsoft、Google、Facebook以及Twitter账号认证用户的内建支持,并且对于支持OAuth的认证服务具有更普遍的支持。第一个步骤是添加NuGet包,包中含有用于ASP.NET Identity的Google专用附件。请在“Package Manager Console(包管理器控制台)”中输入以下命令: Install-Package Microsoft.Owin.Security.Google -version 2.0.2 There are NuGet packages for each of the services that ASP.NET Identity supports, as described in Table 15-9. 对于ASP.NET Identity支持的每一种服务都有相应的NuGet包,如表15-9所示。 Table 15-9. The NuGet Authenticaton Packages 表15-9. NuGet认证包 Name 名称 Description 描述 Microsoft.Owin.Security.Google Authenticates users with Google accounts 用Google账号认证用户 Microsoft.Owin.Security.Facebook Authenticates users with Facebook accounts 用Facebook账号认证用户 Microsoft.Owin.Security.Twitter Authenticates users with Twitter accounts 用Twitter账号认证用户 Microsoft.Owin.Security.MicrosoftAccount Authenticates users with Microsoft accounts 用Microsoft账号认证用户 Microsoft.Owin.Security.OAuth Authenticates users against any OAuth 2.0 service 根据任一OAuth 2.0服务认证用户 Once the package is installed, I enable support for the authentication service in the OWIN startup class, which is defined in the App_Start/IdentityConfig.cs file in the example project. Listing 15-22 shows the change that I have made. 一旦安装了这个包,便可以在OWIN启动类中启用此项认证服务的支持,启动类的定义在示例项目的App_Start/IdentityConfig.cs文件中。清单15-22显示了所做的修改。 Listing 15-22. Enabling Google Authentication in the IdentityConfig.cs File 清单15-22. 在IdentityConfig.cs文件中启用Google认证 using Microsoft.AspNet.Identity;using Microsoft.Owin;using Microsoft.Owin.Security.Cookies;using Owin;using Users.Infrastructure;using Microsoft.Owin.Security.Google;namespace Users {public class IdentityConfig {public void Configuration(IAppBuilder app) {app.CreatePerOwinContext<AppIdentityDbContext>(AppIdentityDbContext.Create);app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);app.CreatePerOwinContext<AppRoleManager>(AppRoleManager.Create); app.UseCookieAuthentication(new CookieAuthenticationOptions {AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,LoginPath = new PathString("/Account/Login"),}); app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);app.UseGoogleAuthentication();} }} Each of the packages that I listed in Table 15-9 contains an extension method that enables the corresponding service. The extension method for the Google service is called UseGoogleAuthentication, and it is called on the IAppBuilder implementation that is passed to the Configuration method. 表15-9所列的每个包都含有启用相应服务的扩展方法。用于Google服务的扩展方法名称为UseGoogleAuthentication,它通过传递给Configuration方法的IAppBuilder实现进行调用。 Next I added a button to the Views/Account/Login.cshtml file, which allows users to log in via Google. You can see the change in Listing 15-23. 下一步骤是在Views/Account/Login.cshtml文件中添加一个按钮,让用户能够通过Google进行登录。所做的修改如清单15-23所示。 Listing 15-23. Adding a Google Login Button to the Login.cshtml File 清单15-23. 在Login.cshtml文件中添加Google登录按钮 @model Users.Models.LoginModel@{ ViewBag.Title = "Login";}<h2>Log In</h2> @Html.ValidationSummary()@using (Html.BeginForm()) {@Html.AntiForgeryToken();<input type="hidden" name="returnUrl" value="@ViewBag.returnUrl" /><div class="form-group"><label>Name</label>@Html.TextBoxFor(x => x.Name, new { @class = "form-control" })</div><div class="form-group"><label>Password</label>@Html.PasswordFor(x => x.Password, new { @class = "form-control" })</div><button class="btn btn-primary" type="submit">Log In</button>}@using (Html.BeginForm("GoogleLogin", "Account")) {<input type="hidden" name="returnUrl" value="@ViewBag.returnUrl" /><button class="btn btn-primary" type="submit">Log In via Google</button>} The new button submits a form that targets the GoogleLogin action on the Account controller. You can see this method—and the other changes I made the controller—in Listing 15-24. 新按钮递交一个表单,目标是Account控制器中的GoogleLogin动作。可从清单15-24中看到该方法,以及在控制器中所做的其他修改。 Listing 15-24. Adding Support for Google Authentication to the AccountController.cs File 清单15-24. 在AccountController.cs文件中添加Google认证支持 using System.Threading.Tasks;using System.Web.Mvc;using Users.Models;using Microsoft.Owin.Security;using System.Security.Claims;using Microsoft.AspNet.Identity;using Microsoft.AspNet.Identity.Owin;using Users.Infrastructure;using System.Web; namespace Users.Controllers {[Authorize]public class AccountController : Controller {[AllowAnonymous]public ActionResult Login(string returnUrl) {if (HttpContext.User.Identity.IsAuthenticated) {return View("Error", new string[] { "Access Denied" });}ViewBag.returnUrl = returnUrl;return View();}[HttpPost][AllowAnonymous][ValidateAntiForgeryToken]public async Task<ActionResult> Login(LoginModel details, string returnUrl) {if (ModelState.IsValid) {AppUser user = await UserManager.FindAsync(details.Name,details.Password);if (user == null) {ModelState.AddModelError("", "Invalid name or password.");} else {ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user,DefaultAuthenticationTypes.ApplicationCookie); ident.AddClaims(LocationClaimsProvider.GetClaims(ident));ident.AddClaims(ClaimsRoles.CreateRolesFromClaims(ident)); AuthManager.SignOut();AuthManager.SignIn(new AuthenticationProperties {IsPersistent = false}, ident);return Redirect(returnUrl);} }ViewBag.returnUrl = returnUrl;return View(details);} [HttpPost][AllowAnonymous]public ActionResult GoogleLogin(string returnUrl) {var properties = new AuthenticationProperties {RedirectUri = Url.Action("GoogleLoginCallback",new { returnUrl = returnUrl})};HttpContext.GetOwinContext().Authentication.Challenge(properties, "Google");return new HttpUnauthorizedResult();}[AllowAnonymous]public async Task<ActionResult> GoogleLoginCallback(string returnUrl) {ExternalLoginInfo loginInfo = await AuthManager.GetExternalLoginInfoAsync();AppUser user = await UserManager.FindAsync(loginInfo.Login);if (user == null) {user = new AppUser {Email = loginInfo.Email,UserName = loginInfo.DefaultUserName,City = Cities.LONDON, Country = Countries.UK};IdentityResult result = await UserManager.CreateAsync(user);if (!result.Succeeded) {return View("Error", result.Errors);} else {result = await UserManager.AddLoginAsync(user.Id, loginInfo.Login);if (!result.Succeeded) {return View("Error", result.Errors);} }}ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user,DefaultAuthenticationTypes.ApplicationCookie);ident.AddClaims(loginInfo.ExternalIdentity.Claims);AuthManager.SignIn(new AuthenticationProperties {IsPersistent = false }, ident);return Redirect(returnUrl ?? "/");}[Authorize]public ActionResult Logout() {AuthManager.SignOut();return RedirectToAction("Index", "Home");}private IAuthenticationManager AuthManager {get {return HttpContext.GetOwinContext().Authentication;} }private AppUserManager UserManager {get {return HttpContext.GetOwinContext().GetUserManager<AppUserManager>();} }} } The GoogleLogin method creates an instance of the AuthenticationProperties class and sets the RedirectUri property to a URL that targets the GoogleLoginCallback action in the same controller. The next part is a magic phrase that causes ASP.NET Identity to respond to an unauthorized error by redirecting the user to the Google authentication page, rather than the one defined by the application: GoogleLogin方法创建了AuthenticationProperties类的一个实例,并为RedirectUri属性设置了一个URL,其目标为同一控制器中的GoogleLoginCallback动作。下一个部分是一个神奇阶段,通过将用户重定向到Google认证页面,而不是应用程序所定义的认证页面,让ASP.NET Identity对未授权的错误进行响应: ...HttpContext.GetOwinContext().Authentication.Challenge(properties, "Google");return new HttpUnauthorizedResult();... This means that when the user clicks the Log In via Google button, their browser is redirected to the Google authentication service and then redirected back to the GoogleLoginCallback action method once they are authenticated. 这意味着,当用户通过点击Google按钮进行登录时,浏览器被重定向到Google的认证服务,一旦在那里认证之后,便被重定向回GoogleLoginCallback动作方法。 I get details of the external login by calling the GetExternalLoginInfoAsync of the IAuthenticationManager implementation, like this: 我通过调用IAuthenticationManager实现的GetExternalLoginInfoAsync方法,我获得了外部登录的细节,如下所示: ...ExternalLoginInfo loginInfo = await AuthManager.GetExternalLoginInfoAsync();... The ExternalLoginInfo class defines the properties shown in Table 15-10. ExternalLoginInfo类定义的属性如表15-10所示: Table 15-10. The Properties Defined by the ExternalLoginInfo Class 表15-10. ExternalLoginInfo类所定义的属性 Name 名称 Description 描述 DefaultUserName Returns the username 返回用户名 Email Returns the e-mail address 返回E-mail地址 ExternalIdentity Returns a ClaimsIdentity that identities the user 返回标识该用户的ClaimsIdentity Login Returns a UserLoginInfo that describes the external login 返回描述外部登录的UserLoginInfo I use the FindAsync method defined by the user manager class to locate the user based on the value of the ExternalLoginInfo.Login property, which returns an AppUser object if the user has been authenticated with the application before: 我使用了由用户管理器类所定义的FindAsync方法,以便根据ExternalLoginInfo.Login属性的值对用户进行定位,如果用户之前在应用程序中已经认证,该属性会返回一个AppUser对象: ...AppUser user = await UserManager.FindAsync(loginInfo.Login);... If the FindAsync method doesn’t return an AppUser object, then I know that this is the first time that this user has logged into the application, so I create a new AppUser object, populate it with values, and save it to the database. I also save details of how the user logged in so that I can find them next time: 如果FindAsync方法返回的不是AppUser对象,那么我便知道这是用户首次登录应用程序,于是便创建了一个新的AppUser对象,填充该对象的值,并将其保存到数据库。我还保存了用户如何登录的细节,以便下次能够找到他们: ...result = await UserManager.AddLoginAsync(user.Id, loginInfo.Login);... All that remains is to generate an identity the user, copy the claims provided by Google, and create an authentication cookie so that the application knows the user has been authenticated: 剩下的事情只是生成该用户的标识了,拷贝Google提供的声明(Claims),并创建一个认证Cookie,以使应用程序知道此用户已认证: ...ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user,DefaultAuthenticationTypes.ApplicationCookie);ident.AddClaims(loginInfo.ExternalIdentity.Claims);AuthManager.SignIn(new AuthenticationProperties { IsPersistent = false }, ident);... 15.4.2 Testing Google Authentication 15.4.2 测试Google认证 There is one further change that I need to make before I can test Google authentication: I need to change the account verification I set up in Chapter 13 because it prevents accounts from being created with e-mail addresses that are not within the example.com domain. Listing 15-25 shows how I removed the verification from the AppUserManager class. 在测试Google认证之前还需要一处修改:需要修改第13章所建立的账号验证,因为它不允许example.com域之外的E-mail地址创建账号。清单15-25显示了如何在AppUserManager类中删除这种验证。 Listing 15-25. Disabling Account Validation in the AppUserManager.cs File 清单15-25. 在AppUserManager.cs文件中取消账号验证 using Microsoft.AspNet.Identity;using Microsoft.AspNet.Identity.EntityFramework;using Microsoft.AspNet.Identity.Owin;using Microsoft.Owin;using Users.Models; namespace Users.Infrastructure {public class AppUserManager : UserManager<AppUser> {public AppUserManager(IUserStore<AppUser> store): base(store) {}public static AppUserManager Create(IdentityFactoryOptions<AppUserManager> options,IOwinContext context) {AppIdentityDbContext db = context.Get<AppIdentityDbContext>();AppUserManager manager = new AppUserManager(new UserStore<AppUser>(db)); manager.PasswordValidator = new CustomPasswordValidator {RequiredLength = 6,RequireNonLetterOrDigit = false,RequireDigit = false,RequireLowercase = true,RequireUppercase = true}; //manager.UserValidator = new CustomUserValidator(manager) {// AllowOnlyAlphanumericUserNames = true,// RequireUniqueEmail = true//};return manager;} }} Tip you can use validation for externally authenticated accounts, but I am just going to disable the feature for simplicity. 提示:也可以使用外部已认证账号的验证,但这里出于简化,取消了这一特性。 To test authentication, start the application, click the Log In via Google button, and provide the credentials for a valid Google account. When you have completed the authentication process, your browser will be redirected back to the application. If you navigate to the /Claims/Index URL, you will be able to see how claims from the Google system have been added to the user’s identity, as shown in Figure 15-7. 为了测试认证,启动应用程序,通过点击“Log In via Google(通过Google登录)”按钮,并提供有效的Google账号凭据。当你完成了认证过程时,浏览器将被重定向回应用程序。如果导航到/Claims/Index URL,便能够看到来自Google系统的声明(Claims),已被添加到用户的标识中了,如图15-7所示。 Figure 15-7. Claims from Google 图15-7. 来自Google的声明(Claims) 15.5 Summary 15.5 小结 In this chapter, I showed you some of the advanced features that ASP.NET Identity supports. I demonstrated the use of custom user properties and how to use database migrations to preserve data when you upgrade the schema to support them. I explained how claims work and how they can be used to create more flexible ways of authorizing users. I finished the chapter by showing you how to authenticate users via Google, which builds on the ideas behind the use of claims. 本章向你演示了ASP.NET Identity所支持的一些高级特性。演示了自定义用户属性的使用,还演示了在升级数据架构时,如何使用数据库迁移保护数据。我解释了声明(Claims)的工作机制,以及如何将它们用于创建更灵活的用户授权方式。最后演示了如何通过Google进行认证结束了本章,这是建立在使用声明(Claims)的思想基础之上的。 本篇文章为转载内容。原文链接:https://blog.csdn.net/gz19871113/article/details/108591802。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-10-28 08:49:21
283
转载
建站模板下载
...用响应式布局,确保跨平台良好展示。模板内含丰富的栏目设置和文章管理功能,并且对SEO优化友好,允许独立配置标题、关键词和描述,便于搜索引擎收录,助力提升网站流量与品牌形象。用户可便捷下载安装,随附详细教程,快速搭建高品质机械设备企业网站。 点我下载 文件大小:7.57 MB 您将下载一个资源包,该资源包内部文件的目录结构如下: 本网站提供模板下载功能,旨在帮助广大用户在工作学习中提升效率、节约时间。 本网站的下载内容来自于互联网。如您发现任何侵犯您权益的内容,请立即告知我们,我们将迅速响应并删除相关内容。 免责声明:站内所有资源仅供个人学习研究及参考之用,严禁将这些资源应用于商业场景。 若擅自商用导致的一切后果,由使用者承担责任。
2023-03-22 19:28:09
294
本站
MySQL
...服务器,简化了安装与配置过程,同时也便于实现跨环境的一致性。 近期,微软Azure云平台推出了针对MySQL的完全托管服务,用户无需关心底层基础设施,只需通过图形化界面或API即可完成数据库的创建、配置及扩展等操作。对于那些关注性能优化和高可用性的用户,可以进一步探索MySQL 8.0中的新特性,如窗口函数、原子DDL操作、资源组管理和CACHING_sha2_password身份验证插件等,以提升数据库的稳定性和安全性。 此外,随着DevOps文化的普及,越来越多的企业采用自动化工具(如Ansible、Chef或Puppet)进行MySQL数据库的运维管理,包括自动备份恢复、监控告警、性能调优等任务,大大提高了工作效率和系统稳定性。 而对于深入学习MySQL的开发者和技术人员,建议阅读官方文档和社区发布的最新教程,了解如何在不同场景下利用MySQL命令行、Workbench图形工具或者PHPMyAdmin等第三方工具进行数据库设计、SQL查询优化以及权限管理等高级实践。同时,跟踪MySQL官方博客和社区论坛上的讨论,及时获取关于安全更新、补丁发布以及最佳实践的最新资讯,确保在享受MySQL强大功能的同时,能够紧跟时代步伐,应对不断变化的技术挑战。
2023-12-12 11:10:15
135
数据库专家
Apache Solr
...如何根据业务需求合理配置ZooKeeper和Solr,以实现最优性能,是每个开发者和运维人员都需要深入研究和实践的主题。建议读者可以参考《ZooKeeper实战》、《Solr权威指南》等专业书籍,结合线上教程和官方文档,了解如何在不同规模和业务场景下对这两个组件进行调优和故障排查,从而构建出既稳定又高效的搜索与数据分析平台。
2023-05-23 17:55:59
497
落叶归根-t
JSON
...ngify()的可配置选项,使得开发者能更灵活地控制序列化结果,提高资源利用率。 此外,尽管JSON在便捷性和效率上有显著优势,但XML在特定场景下仍不可替代,特别是在处理具有复杂结构、需要严格语义约束以及跨平台兼容性要求高的系统中。例如,SOAP协议在企业级服务间的通信中依然广泛采用XML,以实现严格的类型定义和命名空间管理。 综上所述,JSON凭借其简洁易用的特性,在当前主流Web服务和移动应用开发中占据主导地位;然而,XML在特定应用场景下的价值仍然值得重视,两种格式各有优劣,实际选用应根据具体需求来决定。未来,我们期待看到更多关于JSON及其他新型数据交换格式的研究与发展,以适应日新月异的技术变革和市场需求。
2023-10-22 23:34:21
516
程序媛
转载文章
...易用的界面,还针对低配置机器进行了深度优化,确保在保证功能完整的同时提供流畅的操作体验。 与此同时,Linux社区对于桌面环境的探讨与创新从未止步。有开发者正致力于打造更加现代化、跨平台且资源占用更少的新一代桌面环境,如PinePhone上的Phosh项目以及受到广泛关注的Wayland显示服务器协议,它们都可能在未来重塑Linux桌面环境格局。 此外,对于热衷于DIY的用户来说,深入学习如何利用Alacarte或其他工具自定义Linux桌面菜单布局,不仅可以提升日常工作效率,还能彰显个性。实际上,Linux系统的强大之处就在于其开放性和自由度,允许用户根据自己的喜好调整系统每一处细节,从而打造出真正符合个人习惯的工作或娱乐环境。 综上所述,无论是从最新的Linux发行版动态,还是到未来桌面环境发展趋势,抑或是鼓励用户发挥创意进行个性化定制,都在不断强调Linux世界的核心价值——自由、灵活和无限可能性。
2023-07-04 22:18:47
80
转载
Linux
...站正在转向HTTPS协议以提供更安全的数据传输服务。然而,尽管wget对HTTPS支持良好,但在处理证书验证、代理设置以及受限资源下载等方面,用户仍可能遇到一些复杂问题。 例如,当尝试下载具有自签名证书或不受信任证书的HTTPS资源时,wget默认会拒绝连接。为解决此问题,用户需通过--no-check-certificate参数关闭证书验证。此外,wget也支持通过--http-user和--http-password参数进行基本身份验证,这对于访问受密码保护的资源至关重要。 近期新闻中提到,开源工具如wget因其强大的灵活性及跨平台特性,在自动化任务、数据备份、大规模网页抓取等领域持续发挥重要作用。然而,随着网络技术和法律法规的发展,如何合法合规地使用wget这类工具获取并处理网络数据成为新的议题。用户在利用wget的同时,应当充分了解并尊重目标网站的服务条款、robots.txt规则以及各国有关数据抓取和隐私保护的法规要求。 综上所述,wget作为一款强大的命令行下载工具,不仅需要用户掌握其实用技巧,同时也需关注在实际操作过程中可能出现的各类问题以及相应的法律和道德规范。
2023-01-17 22:13:36
146
半夏微凉_t
VUE
...持续火热,以及Visual Studio Code编辑器的广泛应用,二者结合为现代Web开发提供了前所未有的高效工作流。近期,Vue.js官方团队宣布了与VS Code更深度的合作计划,将推出更多专门针对Vue生态系统的插件和工具链优化,以提升开发者的工作效率。 例如,Vetur插件即将迎来重大更新,除了现有的语法高亮、智能提示等功能外,还将支持Vue 3.x的Composition API特性,并且提供更好的错误检测和调试支持。同时,VS Code也将集成更多Vue项目的项目管理和构建工具,如Vue CLI 5的无缝对接,实现一键创建、运行和部署Vue应用。 此外,微软也正在努力改进VS Code对大型Vue项目的性能优化,特别是在处理大量组件和状态管理时,通过内存管理和索引策略的升级,确保编辑器在处理复杂项目时依然保持流畅。 值得一提的是,Vue社区也在积极推动相关的教程资源和技术分享,包括如何充分利用VS Code进行Vue组件化开发、Vue项目的实时预览与热重载等实践技巧,帮助开发者更好地掌握这一强大的开发工具组合,紧跟前端技术发展的步伐。 综上所述,Vue和VS Code的紧密协作不仅提升了前端开发者的实际工作效率,而且顺应了现代Web开发趋势,进一步巩固了它们在前端工具链中的核心地位。无论是初学者还是资深开发者,关注并学习如何有效利用Vue与VS Code的最新功能与最佳实践,都将极大地推动自身技术水平的提升与发展。
2023-10-18 12:42:49
93
码农
HessianRPC
...种基于Hessian协议的远程过程调用(Remote Procedure Call,RPC)技术,它允许在分布式系统中的不同节点之间进行高效、轻量级的对象交换和方法调用。通过采用二进制编码方式,HessianRPC实现了跨平台和跨语言的数据传输,使得Java对象能够方便快捷地在网络间进行序列化和反序列化,从而实现服务之间的通信。 分布式系统 , 分布式系统是由多个独立计算机或组件通过网络连接起来协同工作,共同完成一项任务的计算系统。在本文语境中,HessianRPC应用于分布式系统的场景,如消息传递和服务调用,以解决数据在网络节点间的高效、可靠传输问题。 ClassNotFoundException , 在Java编程环境中,ClassNotFoundException是一个运行时异常,当Java虚拟机或者类加载器试图动态加载一个类,但在指定的类路径下找不到该类的定义时抛出。在使用HessianRPC进行对象序列化和反序列化过程中,如果服务器端没有客户端所序列化对象对应的类信息,则在反序列化时会抛出ClassNotFoundException。为了避免这种情况,需要确保所有相关类信息在序列化与反序列化两端都可用,并正确配置类加载器。
2023-04-06 14:52:47
479
半夏微凉-t
转载文章
...本的新特性解析与实践教程(例如“Python 3.10新特性深度解读”),这些内容将帮助您掌握最新的编程工具和技术趋势。 其次,开源社区对于Python环境搭建和依赖管理不断进行优化升级。比如Anaconda等科学计算平台提供了预编译好的Python环境,简化了复杂环境下的安装配置流程。您可以查阅相关文章,如“利用Anaconda轻松管理和部署Python多版本环境”。 再者,Flask作为轻量级Web服务框架,其应用场景和生态建设日益丰富。近期有报道显示,众多大型企业及项目正逐步采用或迁移至Flask以实现微服务架构,例如“Flask在现代Web开发中的实战应用与案例分析”。同时,Flask社区也发布了诸多插件与扩展,使开发者能够更便捷地构建功能全面的Web应用。 此外,针对数据库支持方面,不妨关注SQLite和MySQL等数据库系统在Python环境下的性能优化方案,以及Python连接数据库时的安全性提升措施,例如阅读“Python数据库操作安全最佳实践:SQLite与MySQL篇”。 综上所述,紧跟Python和Flask的技术更新步伐,探索更高效且安全的开发实践,是每个Python开发者持续提升技能的重要途径。通过以上延伸阅读,希望您能深入理解并熟练运用Python和Flask在实际项目中的能力。
2023-12-21 18:00:00
92
转载
Saiku
...原因: - LDAP配置错误:如果LDAP服务器的URL、端口、认证类型等设置不正确,或者ldap.binddn和ldap.bindpassword的值设置错误,都会导致无法连接到LDAP服务器,从而无法完成身份验证。 - 用户名或密码错误:虽然你确认你的用户名和密码都是正确的,但是在某些情况下,例如你在其他地方修改了密码,或者在LDAP服务器上删除了这个用户的账号,也会导致登录失败。 - Saiku配置错误:如果你的Saiku配置文件中没有正确地设置LDAP集成的相关信息,如ldap.url、ldap.basedn等,也可能会导致登录失败。 3. 解决方案 针对上述可能出现的问题,我们可以采取以下措施来解决: 3.1 检查并修正LDAP配置 首先,我们需要确保LDAP服务器的URL、端口、认证类型等设置是正确的。如果你对这些信息该怎么填拿不准,那就直接翻翻LDAP服务器供应商提供的使用手册,或者更简单点,打个电话、发封邮件咨询他们的技术支持团队,让他们手把手教你搞定。 然后,我们需要检查ldap.binddn和ldap.bindpassword的值是否正确。这两个数值一般是由你们公司的那位“背后大神”——系统管理员来设定的,所以假如你对此一头雾水,不知道它们应该是啥,那就赶紧去找这位“超级英雄”咨询一下吧! 3.2 检查并纠正用户名或密码 如果上面的步骤都不能解决问题,那么可能是你的用户名或密码出了问题。在这种情况下,你需要重新获取正确的用户名和密码。具体来说,你可以联系你的系统管理员,让他们告诉你正确的用户名和密码。如果你在其他地儿改了密码,那千万得记住,这个新密码也得在Saiku上生效才行。 3.3 检查并修正Saiku配置 最后,我们还需要检查你的Saiku配置文件,确保其中包含了正确的LDAP集成相关信息。具体的步骤如下: 首先,打开你的Saiku配置文件(通常是/etc/saiku/pentaho-saiku.properties),然后找到相关的LDAP配置项。这些配置项通常包括ldap.url、ldap.basedn、ldap.username等。 然后,检查这些配置项的值是否正确。如果不正确,你需要将它们更改为正确的值。 3.4 重启Saiku 完成上述所有步骤后,你需要重启Saiku才能使更改生效。实际上,这个操作步骤可能会随着你操作系统和安装环境的变化而有所差异。但通常情况下,你有两个主要的方法来完成它:一是通过命令行这种“黑窗口”式的工具,二是利用服务管理器这个功能强大的家伙进行操作,就像你亲自指挥一支小分队一样去管理你的系统服务~ 4. 结论 总的来说,解决Saiku LDAP集成登录失效的问题需要从多个方面入手,包括检查和修正LDAP配置、用户名或密码,以及检查和修正Saiku配置。希望这篇教程能对你有所帮助。如果你在实践中遇到了其他问题,欢迎随时提问。
2023-12-01 14:45:01
130
月影清风-t
Lua
...篇文章时,我注意到Lua在游戏开发领域的应用日益增多,尤其是实时策略游戏和多人在线游戏,这得益于Lua的高效性、易于上手和模块化的特性。因此,我选择将“Lua在游戏开发中的应用”作为“延伸阅读”的主题。 在游戏开发中,Lua被广泛应用于脚本编写、配置管理、AI行为逻辑设计、状态机管理和游戏服务器脚本等方面。它为开发者提供了快速迭代和灵活调整游戏内容的能力,同时减轻了游戏引擎的负担,让游戏开发者能够专注于游戏的核心逻辑和创意设计。 例如,在实时策略游戏中,Lua可以用来定义单位的行为逻辑、资源管理、建筑建设规则等,通过简单的脚本就能实现复杂的决策树和条件判断,使得游戏AI更加智能和多样。此外,Lua还常用于游戏服务器的脚本,负责处理玩家行为、交易系统、排行榜更新等后台服务,保证游戏的稳定运行和公平竞争环境。 另一方面,Lua在多人在线游戏中也有着不可忽视的作用。它能够帮助开发者快速搭建和调整游戏服务器架构,实现跨平台兼容性,以及处理复杂的网络通信协议和玩家间交互逻辑。通过Lua,开发者可以轻松实现诸如匹配系统、聊天系统、物品交易等关键功能,同时保持代码的简洁和易于维护。 总之,Lua在游戏开发领域的应用不仅提升了开发效率,还增强了游戏的可扩展性和适应性,是现代游戏开发不可或缺的一部分。随着游戏技术的不断进步,Lua在游戏开发中的应用将会越来越广泛,为开发者提供更多的可能性和创新空间。
2024-08-29 16:20:00
89
蝶舞花间
转载文章
...基于Python的跨平台网络诊断工具,能够快速定位并修复网络故障。 进一步探讨Python在网络管理上的潜力,我们可以看到其在企业级网络运维领域的广泛应用。比如,结合Python与SNMP协议可以实现大规模网络设备的集中监控与管理;利用netmiko库,Python能轻松操控多品牌网络设备进行配置备份、批量升级等工作。 此外,Python在网络安全领域也大显身手,诸如自动化渗透测试工具、网络流量分析系统以及恶意行为检测引擎等,均能看到Python的身影。可见,Python以其强大的可扩展性和丰富的第三方库,为各类网络相关问题提供了灵活而高效的解决方案,持续赋能现代生活和各行各业的数字化进程。
2024-01-14 10:28:12
80
转载
.net
...CF是微软为.NET平台提供的一体化通信解决方案,旨在简化不同应用程序之间的互操作性。用WCF这个工具,我们能够亲手打造面向服务的应用程序,甭管是网络协议、消息格式还是传输方式,都能支持多种多样。这样一来,不管平台怎么变,技术栈怎么不同,数据交换都能轻松跨过去,畅通无阻地实现。 2. 创建WCF服务项目 (1)启动Visual Studio,选择新建项目,然后在模板列表中找到“WCF服务库”,点击创建。此刻,你会看到一个默认生成的服务接口(IService1.cs)和其实现类(Service1.svc.cs)。 csharp // IService1.cs [ServiceContract] public interface IService1 { [OperationContract] string GetData(int value); } // Service1.svc.cs public class Service1 : IService1 { public string GetData(int value) { return string.Format("You entered: {0}", value); } } 这段代码展示了如何定义一个基本的WCF服务契约(通过ServiceContract属性标记接口)以及其实现(通过实现该接口)。嘿,你知道吗?在编程里头,有个叫做OperationContract的小家伙可厉害了。它专门用来标记接口里的某个方法,告诉外界:“瞧瞧,这个方法就是我们对外开放的服务操作!”这样说是不是感觉更接地气啦? 3. 配置WCF服务 打开App.config文件,你会发现WCF服务的核心配置信息都在这里。例如: xml 这部分配置说明了服务的终结点信息,包括地址、绑定和合同。在这儿,我们捣鼓出了一个借助HTTP搭建的基础接口,专门用来应对各种服务请求;另外还搞了个小家伙,它的任务是负责交换那些元数据信息。 4. 部署与调用WCF服务 完成服务编写和配置后,将项目部署到IIS或直接运行调试即可。客户端想要调用这个服务,有俩种接地气的方式:一种是直接在程序里头添加服务引用,另一种则是巧妙地运用ChannelFactory这个工具来实现调用。就像我们平时点外卖,既可以收藏常去的店铺快速下单,也可以灵活搜索各种渠道找到并订购心仪美食一样。下面是一个简单的客户端调用示例: csharp // 添加服务引用后自动生成的Client代理类 var client = new Service1Client(); var result = client.GetData(123); Console.WriteLine(result); // 输出 "You entered: 123" client.Close(); 这里,我们创建了一个服务客户端实例,并调用了GetData方法,实现了与服务端的交互。 5. 进阶探讨 当然,WCF的功能远不止于此,还包括安全性、事务处理、可靠会话、多线程并发控制等诸多高级特性。比如,我们可以为服务操作添加安全性验证: csharp [OperationContract] [PrincipalPermission(SecurityAction.Demand, Role = "Admin")] string SecureGetData(int value); 这段代码表明只有角色为"Admin"的用户才能访问SecureGetData方法,体现了WCF的安全性优势。 总的来说,WCF在.NET中为我们提供了便捷而强大的Web服务开发工具,无论是初级开发者还是资深工程师,都需要对其有足够的理解和熟练应用。在实践中不断探索和尝试,相信你会越来越感受到WCF的魅力所在!
2023-07-18 11:00:57
456
红尘漫步
DorisDB
...时,为解决跨版本、跨平台数据库互操作的问题,ODBC等标准接口技术的作用日益凸显。例如,微软近日推出了新版ODBC驱动程序,增强了对最新SQL Server以及其他多种主流数据库的支持,通过优化的连接性能和更全面的API支持,大大降低了因版本不匹配带来的开发与运维难度。 此外,业内专家建议,在进行数据库版本升级时,除了技术层面的考量,企业还应结合业务需求、成本预算以及潜在风险进行全面评估,并制定详细的升级规划和应急预案,确保在提升系统性能的同时,最大限度地保障业务连续性和数据安全性。通过不断跟进行业动态,深入理解并应用最新的数据库技术成果,企业和开发者将能更好地应对数据库版本不匹配等挑战,实现更加稳定、高效的数据库环境构建与运维。
2023-03-28 13:12:45
429
笑傲江湖-t
HessianRPC
...迟,各种二进制RPC协议应运而生。你知道吗,Hessian RPC协议这个家伙可厉害了!它那轻巧灵活的身段、飞一般的速度表现,还有那跨平台无缝切换的能力,在咱们行业中可是火得不得了,被大家伙广泛应用着呢! 然而,对于Hessian来说,其默认使用的文本格式在数据传输时可能存在性能瓶颈。这个时候,我们可以选择开启Hessian RPC协议这个小功能,让它用二进制的方式帮我们交换数据。这样一来,Hessian的性能就能蹭蹭地往上提升不少! 二、Hessian RPC协议的基本原理 Hessian是一种Java语言编写的高性能二进制序列化协议,主要用于对象的远程调用和数据交换。它就像个神奇的小帮手,能将Java对象瞬间变成二进制的小溪流,然后嗖地一下穿越网络,让数据交换变得更迅捷、更高效。 Hessian RPC协议是在Hessian协议的基础上扩展出来的,它提供了完整的RPC框架,包括请求/响应模型、错误处理机制、缓存管理等功能。跟普通的Hessian相比,Hessian RPC协议就像个升级版的小能手,它的可扩展性和易用性简直不要太赞,让你在捣鼓分布式系统设计和开发时,感觉轻松愉快、如虎添翼。 三、启用Hessian RPC协议 在Hessian中,我们可以通过设置hessian.config.useBinaryProtocol属性为true,来启用Hessian RPC协议的二进制模式。具体代码如下: java // 设置Hessian配置 HessianConfig config = new HessianConfig(); config.setUseBinaryProtocol(true); // 创建Hessian服务端对象 HessianService service = new HessianService(config); service.export(new EchoServiceImpl()); 上述代码首先创建了一个Hessian配置对象,并将其useBinaryProtocol属性设置为true,表示启用二进制模式。接着,我们捣鼓出一个Hessian服务端的小家伙,把它帅气地挂到网上,这样一来客户端的伙伴们就能随时来调用它了。 四、使用Hessian RPC协议进行数据交换 在启用Hessian RPC协议后,我们就可以使用二进制格式进行数据交换了。下面是一个简单的示例: java // 创建Hessian客户端对象 HessianClient client = new HessianClient("http://localhost:8080/hessian"); // 调用服务端方法并获取结果 EchoResponse response = (EchoResponse) client.invoke("echo", "Hello, Hessian!"); System.out.println(response.getMessage()); // 输出:Hello, Hessian! 上述代码首先创建了一个Hessian客户端对象,并连接到了运行在本地主机上的Hessian服务端。然后,我们调用了服务端的echo方法,并传入了一个字符串参数。最后,我们将服务端返回的结果打印出来。 五、结论 总的来说,通过启用Hessian RPC协议,我们可以将Hessian的默认文本格式转换为高效的二进制格式,从而显著提高Hessian的性能。另外,Hessian RPC协议还带了一整套超给力的功能,这对我们更顺溜地设计和搭建分布式系统可是大有裨益! 在未来的工作中,我们将继续探索Hessian和Hessian RPC协议的更多特性,以及它们在实际应用中的最佳实践。不久的将来,我可以肯定地跟你说,会有越来越多的企业开始拥抱Hessian和Hessian RPC协议,为啥呢?因为它们能让网络应用跑得更快、更稳、更靠谱。这样一来,构建出的网络服务就更加顶呱呱了!
2023-01-11 23:44:57
444
雪落无痕-t
转载文章
...rosoft Visual Studio Code(VS Code)通过其Python扩展插件新增了对Jupyter Notebook的支持,使得开发者能够在熟悉的代码编辑器环境中直接创建、运行和分享Jupyter Notebook项目,大大提升了数据科学家和机器学习工程师的工作效率。 其次,PyCharm 2023.1版本发布,该版本强化了对异步编程的支持,并优化了Type Checking与类型提示功能,为Python开发者提供更为智能和高效的编程体验。同时,PyCharm继续深化对Django、Flask等主流Web框架的支持,以及对大型项目的管理和调试能力。 此外,Anaconda近期发布的Conda 4.11版,增强了对Mamba协议的支持,进一步加快了包管理的速度,特别是对于包含大量依赖项的数据科学项目,显著提高了环境配置的时间效率。 在在线教育领域,CodeHS新近推出了针对Python初学者的互动教程,结合Python Tutor的理念,以游戏化的方式教授编程基础知识,让更多学生能够轻松入门Python编程。 Python社区的发展永不停歇,这些工具和平台的持续更新与迭代,不仅反映出Python在各领域的广泛应用,也预示着未来Python开发将更加便捷高效,助力开发者们实现更多的创新与突破。
2023-11-14 09:38:26
43
转载
ActiveMQ
...消息中间件,可实现跨平台、异步、可靠的消息传递。它的最大亮点就是超级稳定、能够巧妙地分配任务负荷,还有对多种通讯协议的全面支持,像是AMQP、STOMP、MQTT这些,样样精通。 java // 创建ActiveMQ连接工厂 ConnectionFactory factory = new ActiveMQConnectionFactory("tcp://localhost:61616"); // 从连接工厂创建连接 Connection connection = factory.createConnection(); connection.start(); // 创建会话 Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); // 创建目标队列 Destination destination = session.createQueue("MyQueue"); // 创建生产者 MessageProducer producer = session.createProducer(destination); // 创建并发送消息 TextMessage message = session.createTextMessage("Hello from ActiveMQ!"); producer.send(message); 上述代码展示了如何使用Java API创建一个简单的ActiveMQ生产者,向名为"MyQueue"的队列发送一条消息。 3. Camel与ActiveMQ的集成 Apache Camel通过提供丰富的组件库来简化集成任务,其中当然也包含了对ActiveMQ的出色支持。使用Camel-ActiveMQ这个小玩意儿,我们就能轻轻松松地在Camel的路由规则里头,用ActiveMQ来发送和接收消息,就像玩儿一样简单! java from("timer:tick?period=5000") // 每5秒触发一次 .setBody(constant("Hello Camel with ActiveMQ!")) .to("activemq:queue:MyQueue"); // 将消息发送到ActiveMQ队列 from("activemq:queue:MyQueue") // 从ActiveMQ队列消费消息 .log("Received message: ${body}") .to("mock:result"); // 将消息转发至Mock endpoint用于测试 这段Camel路由配置清晰地展现了如何通过Camel定时器触发消息产生,并将其发送至ActiveMQ队列,同时又设置了一个消费者从该队列中拉取消息并打印处理。 4. Camel集成ActiveMQ的优势及应用场景 通过Camel与ActiveMQ的集成,开发者可以利用Camel的强大路由能力,实现复杂的消息流转逻辑,如内容过滤、转换、分发等。此外,Camel还提供了健壮的错误处理机制,使得整个消息流更具鲁棒性。 例如,在微服务架构下,多个服务间的数据同步、事件通知等问题可以通过ActiveMQ与Camel的结合得到优雅解决。当某个服务干完活儿,处理完了业务,它只需要轻轻松松地把结果信息发布到特定的那个“消息主题”或者“队列”里头。这样一来,其他那些有关联的服务就能像订报纸一样,实时获取到这些新鲜出炉的信息。这就像是大家各忙各的,但又能及时知道彼此的工作进展,既解耦了服务之间的紧密依赖,又实现了异步通信,让整个系统运行得更加灵活、高效。 5. 结语 总的来说,Apache Camel与ActiveMQ的集成极大地扩展了消息驱动系统的可能性,赋予开发者以更高层次的抽象去设计和实现复杂的集成场景。这种联手合作的方式,就像两个超级英雄组队,让整个系统变得身手更加矫健、灵活多变,而且还能够随需应变地扩展升级。这样一来,咱们每天的开发工作简直像是坐上了火箭,效率嗖嗖往上升,维护成本也像滑梯一样唰唰降低,真是省时省力又省心呐!当我们面对大规模、多组件的分布式系统时,不妨尝试借助于Camel和ActiveMQ的力量,让消息传递变得更简单、更强大。
2023-05-29 14:05:13
552
灵动之光
站内搜索
用于搜索本网站内部文章,支持栏目切换。
知识学习
实践的时候请根据实际情况谨慎操作。
随机学习一条linux命令:
set -o vi 或 set -o emacs
- 更改bash shell的命令行编辑模式为vi或emacs风格。
推荐内容
推荐本栏目内的其它文章,看看还有哪些文章让你感兴趣。
2023-04-28
2023-08-09
2023-06-18
2023-04-14
2023-02-18
2023-04-17
2024-01-11
2023-10-03
2023-09-09
2023-06-13
2023-08-07
2023-03-11
历史内容
快速导航到对应月份的历史文章列表。
随便看看
拉到页底了吧,随便看看还有哪些文章你可能感兴趣。
时光飞逝
"流光容易把人抛,红了樱桃,绿了芭蕉。"