
引子 最近根据业务的一些需求,所以放弃从快递鸟对接去电子面单,转而直接对接韵达开发平台:http://open.yundasys.com/ ,中间踩了一些坑,借此做了一个小案例给大伙,瞅瞅,若有需改进之处,还请指出!!! 废话不多数:首先咱先对韵达的一些接口参数了解清楚: 当然附上地址:http://open.yundasys.com/index.php?g=&m=ApiTools&a=exm 还有接口的一些SDK文件地址,这个就各位观众大老爷们自己去看了:http://open.yundasys.com/index.php?g=&m=ApiTools&a=apps&id=14 解决方案 上代码走起:基础参数的模型 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { //请求参数 class RequestVO { /// <summary> /// XML数据内容 /// </summary> public string xmldata { get; set; } /// <summary> /// 合作社区ID,由韵达给大客户提供 /// </summary> public string partnerid { get; set; } /// <summary> /// 密码 /// </summary> public string password { get; set; } /// <summary> /// 数据请求类型,如request=data;其中data表示下单,详细请见request字典表 /// </summary> public string request { get; set; } /// <summary> /// 请求的版本,当前版本为1.0 /// </summary> public string version { get; set; } } } 主体参数模型 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml.Serialization; namespace ConsoleApplication2 { /// <summary> /// 数据体 /// </summary> public class Orders { [XmlElement("order")] public List<Order> order { get; set; } } /// <summary> /// 韵达取号订单信息 /// </summary> public class Order { /// <summary> /// 订单唯一序列号 /// </summary> public string order_serial_no { get; set; } /// <summary> /// 大客户系统订单的订单号 /// </summary> public string khddh { get; set; } /// <summary> /// 内部参考号,供大客户自己使用,可以是客户的客户编号 /// </summary> public string nbckh { get; set; } /// <summary> /// 单号 /// </summary> public string mailno { get; set; } /// <summary> /// 发件人 /// </summary> [XmlElement("sender")] public Sender sender { get; set; } /// <summary> /// 收件人 /// </summary> [XmlElement("receiver")] public Receiver receiver { get; set; } /// <summary> /// 物品重量 /// </summary> public long weight { get; set; } /// <summary> /// 尺寸,格式(长,宽,高),单位cm /// </summary> public string size { get; set; } /// <summary> /// 货物金额 /// </summary> public decimal value { get; set; } /// <summary> /// 商品集合 /// </summary> [XmlElement("items")] public Items items { get; set; } /// <summary> /// 订单备注 /// </summary> public string remark { get; set; } /// <summary> /// 可以自定义显示信息1 /// </summary> public string cus_area1 { get; set; } /// <summary> /// 可以自定义显示信息2 /// </summary> public string cus_area2 { get; set; } } public class Sender { /// <summary> /// 姓名 /// </summary> public string name { get; set; } /// <summary> /// 公司 /// </summary> public string company { get; set; } /// <summary> /// 严格按照国家行政区划,省市区三级,逗号分隔。示例上海市,上海市,青浦区(cod订单必填) /// </summary> public string city { get; set; } /// <summary> /// 需要将省市区划信息加上,例如:上海市,上海市,青浦区盈港东路7766号 /// </summary> public string address { get; set; } /// <summary> /// 邮编 /// </summary> public string postcode { get; set; } /// <summary> /// 固定电话 /// </summary> public string phone { get; set; } /// <summary> /// 移动电话固定电话或移动电话至少填一项 /// </summary> public string mobile { get; set; } public string branch { get; set; } } public class Receiver { /// <summary> /// 姓名 /// </summary> public string name { get; set; } /// <summary> /// 公司 /// </summary> public string company { get; set; } /// <summary> /// 严格按照国家行政区划,省市区三级,逗号分隔。示例上海市,上海市,青浦区(cod订单必填) /// </summary> public string city { get; set; } /// <summary> /// 需要将省市区划信息加上,例如:上海市,上海市,青浦区盈港东路7766号 /// </summary> public string address { get; set; } /// <summary> /// 邮编 /// </summary> public string postcode { get; set; } /// <summary> /// 固定电话 /// </summary> public string phone { get; set; } /// <summary> /// 移动电话固定电话或移动电话至少填一项 /// </summary> public string mobile { get; set; } public string branch { get; set; } } /// <summary> /// 明细集合 /// </summary> public class Items { [XmlElement("item")] public List<Item> item { get; set; } } /// <summary> /// 明细信息 /// </summary> public class Item { /// <summary> /// 商品名称 /// </summary> public string name { get; set; } /// <summary> /// 商品数量 /// </summary> public int number { get; set; } /// <summary> /// 商品备注 /// </summary> public string remark { get; set; } } } 请求方法: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Web; using System.IO; using System.Collections.Specialized; using System.Net; namespace ConsoleApplication1 { /// <summary> /// POST提交 /// </summary> class HttpClient { /// <summary> /// /// </summary> /// <param name="url"></param> /// <param name="parameters"></param> /// <returns></returns> public static HttpWebResponse post(String url, IDictionary<string, string> parameters) { HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest; request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; //如果需要POST数据 if (!(parameters == null || parameters.Count == 0)) { StringBuilder buffer = new StringBuilder(); int i = 0; foreach (string key in parameters.Keys) { if (i > 0) { buffer.AppendFormat("&{0}={1}", key, parameters[key]); } else { buffer.AppendFormat("{0}={1}", key, parameters[key]); } i++; } byte[] data = Encoding.UTF8.GetBytes(buffer.ToString()); using (Stream stream = request.GetRequestStream()) { stream.Write(data, 0, data.Length); } } return request.GetResponse() as HttpWebResponse; } /// <summary> /// /// </summary> /// <param name="url"></param> /// <param name="data"></param> /// <returns></returns> public static String post(String url, String postdata) { try { HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest; request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; byte[] data = Encoding.UTF8.GetBytes(postdata.ToString()); using (Stream stream = request.GetRequestStream()) { stream.Write(data, 0, data.Length); } HttpWebResponse response = request.GetResponse() as HttpWebResponse; StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.UTF8); string outMessage = sr.ReadToEnd(); sr.Close(); return outMessage; } catch (Exception ex) { throw ex; } } } } 要求 当前按照SDK的要求: 请求报文说明: 1. 数据传输以HTTP POST方式发送,数据字符集一律采用UTF-8 2. xmldata首先需要进行base64编码 3. validation的效验方式采用 MD5(xmldata + partnerid + 密码),这里的加号为字符串连接符号。 4. 所有参数最终均须在完成数据转换后进行URL编码。 请求报文详细解释: 1.假设partnerid为YUNDA;密码为123456;xmldata内容为 <order></order> 2.xmldata经过base64编码以后变成PG9yZGVyPjwvb3JkZXI+ 3.那么要签名的内容为PG9yZGVyPjwvb3JkZXI+YUNDA123456,经过md5后的内容就为f197e870a12528e38cb483b4e371f4ea 4.然后再对xmldata经过URL编码,得到字符串PG9yZGVyPjwvb3JkZXI%2B 5.同样需要对其他字段进行URL编码,否则可能会影响POST传递,具体请参见HTTP POST传输协议 6.最终要发送的数据为: partnerid=YUNDA&version=1.0&request=data&xmldata=PG9yZGVyPjwvb3JkZXI%2B&validation=f197e870a12528e38cb483b4e371f4ea 不拉不拉不拉,一大堆,大老爷们自己去看,这些数据转换的方法我直接贴出: using System; using System.Text; using System.Web; using System.IO; using System.Xml.Serialization; namespace ConsoleApplication1 { class DataTransform { /// <summary> /// 组装主体内容 /// </summary> /// <param name="requestVO"></param> /// <returns></returns> public static String signData(RequestVO requestVO) { String xmldata = Convert.ToBase64String(System.Text.Encoding.GetEncoding("UTF-8").GetBytes(requestVO.xmldata)); string validation = System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(xmldata + requestVO.partnerid + requestVO.password, "MD5").ToLower(); string signdata = "partnerid=" + requestVO.partnerid + "&version=" + requestVO.version + "&request=" + requestVO.request + "&xmldata=" + HttpUtility.UrlEncode(xmldata) + "&validation=" + validation; return signdata; } /// <summary> /// 内容数据转换XML /// </summary> /// <param name="type"></param> /// <param name="obj"></param> /// <returns></returns> public static String obj2Xml(Type type, Object obj) { XmlSerializer xml = new XmlSerializer(type); String xmldata = ""; using (MemoryStream stream = new MemoryStream()) { try { xml.Serialize(stream, obj); xmldata = Encoding.UTF8.GetString(stream.GetBuffer(), 0, (int)stream.Length); } catch (Exception) { throw; } } return xmldata; } /// <summary> /// 内容清洗转换 /// </summary> /// <param name="xml"></param> /// <returns></returns> public static string xmlformat(string xml) { try { System.Xml.XmlDocument doc = new System.Xml.XmlDocument(); doc.LoadXml(xml); System.IO.StringWriter sw = new System.IO.StringWriter(); using (System.Xml.XmlTextWriter writer = new System.Xml.XmlTextWriter(sw)) { writer.Indentation = 2; // the Indentation writer.Formatting = System.Xml.Formatting.Indented; doc.WriteContentTo(writer); writer.Close(); } return sw.ToString(); } catch (Exception ex) { return xml; } } } } 哈哈看了这么多了 咱还没看到请求电子面单的方法是吧 别急 这个类是我自己整合的在项目里的,大老爷们先看看有不足之处 指点指点,应该能看明白!哈哈!案例的是winfrom,这个类没有用上,方法我就不贴出来了,大佬自己去最底下载吧!!! using Commons.BLL; using Commons.Model; using Commons.Settings; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Text; using System.Threading.Tasks; using System.Web; using System.Xml; using System.Xml.Serialization; namespace Commons.Helpers { public class YunDaApiHelper { SettingService _settingService=new SettingService(); /// <summary> /// 韵达电子面单请求url /// </summary> private static string _createYunDaUrl; /// <summary> /// 取消韵达电子面单url /// </summary> private static string _colseYunDaUrl; //韵达ID,密码 private static string _partnerid; private static string _password; //发件人信息 public static string FHCompany; public static string FHName; public static string FHMobile; public static string FHProvinceName; public static string FHCityName; public static string FHExpAreaName; public static string FHAddress; public YunDaApiHelper() {//_createYunDaUrl = "http://orderdev.yundasys.com:10110/cus_order/order_interface/interface_receive_order__mailno.php";//测试 //_colseYunDaUrl = "http://orderdev.yundasys.com:10110/cus_order/order_interface/interface_cancel_order.php";//测试 var settings = _settingService.LoadSetting<KdniaoSettings>(); _createYunDaUrl = settings.CreateYunDaUrl; _colseYunDaUrl = settings.ColseYunDaUrl; FHCompany = settings.FHCompany; FHName = settings.FHName; FHMobile = settings.FHMobile; FHProvinceName = settings.FHProvinceName; FHCityName = settings.FHCityName; FHExpAreaName = settings.FHExpAreaName; FHAddress = settings.FHAddress; _partnerid = settings.YdPartnerId; _password = settings.YdPassword; } /// <summary> /// 申请韵达电子面单 /// </summary> /// <param name="order"></param> /// <returns></returns> public YunDaResult CreateYunDaNo(Order order) { var model = new YDOrderModel(); var send = new YDSender { name = FHName,//发货人名称 company = FHCompany,//发货人公司 mobile = FHMobile,//发货人移动电话或手机 address = FHAddress,//发货人地址,需要将省市区划信息加上,例如:上海市,上海市,青浦区盈港东路7766号 postcode = "510000", //邮编 city = FHProvinceName + FHCityName + FHExpAreaName,//严格按照国家行政区划,省市区三级,逗号分隔。示例上海市,上海市,青浦区(cod订单必填) phone = "",//固话 branch = "" }; order.sender = send; model.order = order; try { var xml = Obj2Xml(typeof (YDOrderModel), model); var requestVo = new YunDaRequestModel { xmldata = xml, partnerid = _partnerid, password = _password, version = "1.0", request = "data" }; var data = SignData(requestVo); var result = Post(_createYunDaUrl, data); var msgBody = new XmlDocument(); msgBody.LoadXml(result); var status = GetXmlValue(msgBody, "status"); var dto = new YunDaResult { status = Convert.ToInt32(status), order_serial_no = GetXmlValue(msgBody, "order_serial_no"), msg = GetXmlValue(msgBody, "msg"), mail_no = GetXmlValue(msgBody, "mail_no") }; return dto; } catch (Exception ex) { var dto = new YunDaResult { status = (int) CustomBoolean.False, msg = ex.ToString() }; return dto; } } /// <summary> /// 取消韵达电子面单 /// </summary> /// <param name="xml"></param> /// <returns></returns> public YunDaResult ColseYunDaNo(string xml) { var requestVo = new YunDaRequestModel { xmldata = Xmlformat(xml), partnerid = _partnerid, password = _password, version = "1.0", request = "cancel_order" }; try { var data = SignData(requestVo); var result = Post(_colseYunDaUrl, data); var msgBody = new XmlDocument(); msgBody.LoadXml(result); var dto = new YunDaResult { status = Convert.ToInt32(GetXmlValue(msgBody, "status")), order_serial_no = GetXmlValue(msgBody, "order_serial_no"), msg = GetXmlValue(msgBody, "msg") }; return dto; } catch (Exception ex) { var dto = new YunDaResult { status = (int) CustomBoolean.False, msg = ex.ToString() }; return dto; } } #region 组装数据以及转化xml数据 /// <summary> /// 组装主体内容 /// </summary> /// <param name="requestVo"></param> /// <returns></returns> public static string SignData(YunDaRequestModel requestVo) { var xmldata = Convert.ToBase64String(System.Text.Encoding.GetEncoding("UTF-8").GetBytes(requestVo.xmldata)); var validation = System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(xmldata + requestVo.partnerid + requestVo.password, "MD5").ToLower(); var signdata = "partnerid=" + requestVo.partnerid + "&version=" + requestVo.version + "&request=" + requestVo.request + "&xmldata=" + HttpUtility.UrlEncode(xmldata) + "&validation=" + validation; return signdata; } /// <summary> /// 内容数据转换XML /// </summary> /// <param name="type"></param> /// <param name="obj"></param> /// <returns></returns> public static string Obj2Xml(Type type, object obj) { var xml = new XmlSerializer(type); var xmldata = ""; using (var stream = new MemoryStream()) { try { xml.Serialize(stream, obj); xmldata = Encoding.UTF8.GetString(stream.GetBuffer(), 0, (int)stream.Length); } catch (Exception) { throw; } } return xmldata; } /// <summary> /// 内容清洗转换 /// </summary> /// <param name="xml"></param> /// <returns></returns> public static string Xmlformat(string xml) { try { var doc = new System.Xml.XmlDocument(); doc.LoadXml(xml); var sw = new System.IO.StringWriter(); using (var writer = new System.Xml.XmlTextWriter(sw)) { writer.Indentation = 2; // the Indentation writer.Formatting = System.Xml.Formatting.Indented; doc.WriteContentTo(writer); writer.Close(); } return sw.ToString(); } catch (Exception ex) { return xml; } } #endregion #region Post数据请求 public static HttpWebResponse Post(string url, IDictionary<string, string> parameters) { var request = WebRequest.Create(url) as HttpWebRequest; request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; //如果需要POST数据 if (!(parameters == null || parameters.Count == 0)) { var buffer = new StringBuilder(); var i = 0; foreach (var key in parameters.Keys) { buffer.AppendFormat(i > 0 ? "&{0}={1}" : "{0}={1}", key, parameters[key]); i++; } var data = Encoding.UTF8.GetBytes(buffer.ToString()); using (var stream = request.GetRequestStream()) { stream.Write(data, 0, data.Length); } } return request.GetResponse() as HttpWebResponse; } public static string Post(string url, string postdata) { try { var request = WebRequest.Create(url) as HttpWebRequest; request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; var data = Encoding.UTF8.GetBytes(postdata.ToString()); using (Stream stream = request.GetRequestStream()) { stream.Write(data, 0, data.Length); } var response = request.GetResponse() as HttpWebResponse; var sr = new StreamReader(response.GetResponseStream(), Encoding.UTF8); var outMessage = sr.ReadToEnd(); sr.Close(); return outMessage; } catch (Exception ex) { throw ex; } } #endregion #region MyRegion /// <summary> /// XML读取对应的值 /// </summary> /// <param name="msgBody">xml</param> /// <param name="nodeName">节点名称</param> /// <returns>返回节点值</returns> public static string GetXmlValue(XmlDocument msgBody, string nodeName) { var fromUserName = msgBody.GetElementsByTagName(nodeName).Item(0); return fromUserName?.InnerText; } #endregion } } SettingService 这个是系统配置参数,应该没毛病哈哈!so,下边咱来看看案例的界面 注 账号:韵达的客户号 密码:是韵达二维码VIP客户端的《接口联调密码》 结语 案例很简单,但是有包含蛮多东东的,各位大佬只要是搞通一个,那估摸着就都没问题了! 链接:https://pan.baidu.com/s/1T3X8-TLorn5R8nZfpKkqOg 密码:m645 ------地址要是挂了,各位直接联系我哈! 好了!各位大老爷觉着这篇文章要是不错就点个赞咯
//Cookie:是一个客户端状态保持机制,(网站的数据是存在客户端),与隐藏域与ViewState对象都属于这种客户端状态保持,Cookie中存储的是关于网站相关的文本字符串数据。Cookie的存储方式有两种,如果不指定过期时间,那么存储在客户端浏览器内存中,如果指定了过期时间,那么存储在客户端的磁盘上。Cookie是与具体的网站有关的,如果我们将Cookie设置了过期时间,那么当用户在指定时间内访问我们的网站,那么属于我们网站的Cookie数据会放在请求报文中发送过来,其它网站的Cookie不会发送。 //创建客户端 //Response.Cookies["cp1"].Value = "Itcast"; ////获取Cookie的值 //if (Request.Cookies["cp1"] != null) //{ // Response.Write(Request.Cookies["cp1"].Value); //} ////创建cookie并指定过期时间,有时间的话是保存在磁盘中 //Response.Cookies["cp2"].Value = "laowang"; //Response.Cookies["cp2"].Expires = DateTime.Now.AddDays(3); ////删除cookie //Response.Cookies["cp2"].Expires = DateTime.Now.AddDays(-1); ////cookie跨域(域名) //Response.Cookies["cp3"].Value = "laowang"; ////Response.Cookies["cp3"].Domain = "";//设置主域的。比如xxx.com //Response.Cookies["cp3"].Path = "/2015-5-31"; //Response.Cookies["cp3"].Expires = DateTime.Now.AddDays(3); //另外一种创建Cookie的方式。 HttpCookie cookie1 = new HttpCookie("cp4","sssss"); cookie1.Expires = DateTime.Now.AddDays(3); Response.Cookies.Add(cookie1);
SELECT 表名=case when a.colorder=1 then d.name else '' end, 表说明=case when a.colorder=1 then isnull(f.value,'') else '' end, 字段序号=a.colorder, 字段名=a.name, 标识=case when COLUMNPROPERTY( a.id,a.name,'IsIdentity')=1 then '√'else '' end, 主键=case when exists(SELECT 1 FROM sysobjects where xtype='PK' and name in ( SELECT name FROM sysindexes WHERE indid in( SELECT indid FROM sysindexkeys WHERE id = a.id AND colid=a.colid ))) then '√' else '' end, 类型=b.name, 占用字节数=a.length, 长度=COLUMNPROPERTY(a.id,a.name,'PRECISION'), 小数位数=isnull(COLUMNPROPERTY(a.id,a.name,'Scale'),0), 允许空=case when a.isnullable=1 then '√'else '' end, 默认值=isnull(e.text,''), 字段说明=isnull(g.[value],'') FROM syscolumns a left join systypes b on a.xusertype=b.xusertype inner join sysobjects d on a.id=d.id and d.xtype='U' and d.name<>'dtproperties' left join syscomments e on a.cdefault=e.id left join sys.extended_properties g on a.id=g.major_id and a.colid=g.minor_id left join sys.extended_properties f on d.id=f.major_id and f.minor_id=0 --where d.name='orders' --如果只查询指定表,加上此条件 order by a.id,a.colorder
ReturnUrl实现 我们要实现returnUrl,我们需要在注册(Register)方法中接收传进的returnUrl并给它默认值null,然后将它保存在ViewData里面 然后我们定义一个内部方法来判断跳转returnUrl //内部跳转 private IActionResult RedirectToLocal(string returnUrl) { if (Url.IsLocalUrl(returnUrl)) {//如果是本地 return Redirect(returnUrl); } return RedirectToAction(nameof(HomeController.Index),"Home"); } 然后我们需要在Register的HttpPost方法中,在注册成功后进行跳转到returnUrl 接下来我们修改Register.cshtml 完整Register.cshtml代码: @{ ViewData["Title"] = "Register";} @using MvcCookieAuthSample.ViewModels;@model RegisterViewModel; <h2>@ViewData["Title"]</h2><h3>@ViewData["Message"]</h3> <div class="row"> <div class="col-md-4"> @* 这里将asp-route-returnUrl="@ViewData["returnUrl"],就可以在进行register的post请求的时候接收到returnUrl *@ <form method="post" asp-route-returnUrl="@ViewData["returnUrl"]"> <h4>Create a new account.</h4> <hr /> <div class="form-group"> <label asp-for="Email"></label> <input asp-for="Email" class="form-control" /> </div> <div class="form-group"> <label asp-for="Password"></label> <input asp-for="Password" class="form-control" /> </div> <div class="form-group"> <label asp-for="ConfirmedPassword"></label> <input asp-for="ConfirmedPassword" class="form-control" /> </div> <button type="submit" class="btn btn-default">Register</button> </form> </div></div> 接下来登陆的地方我们也需要修改一下,我们需要在登陆(Login)方法中接收传进的returnUrl并给它默认值null,然后将它保存在ViewData里面 然后我们需要在Login的HttpPost方法中,在注册成功后进行跳转到returnUrl 接下来我们修改Login.cshtml 完整Login.cshtml代码: @{ ViewData["Title"] = "Login";} @using MvcCookieAuthSample.ViewModels;@model RegisterViewModel; <div class="row"> <div class="col-md-4"> <section> @* 这里将asp-route-returnUrl="@ViewData["returnUrl"],就可以在进行Login的post请求的时候接收到returnUrl *@ <form method="post" asp-controller="Account" asp-action="Login" asp-route-returnUrl="@ViewData["returnUrl"]"> <h4>Use a local account to log in.</h4> <hr /> <div class="form-group"> <label asp-for="Email"></label> <input asp-for="Email" class="form-control" /> </div> <div class="form-group"> <label asp-for="Password"></label> <input asp-for="Password" type="password" class="form-control" /> </div> <div class="form-group"> <button type="submit" class="btn btn-default">Log in</button> </div> </form> </section> </div></div> 然后我们就可以实现登陆/注册后Url进行跳转到之前的页面。 Model后端验证 我们可以通过给ViewModel的属性加头标签来进行Model后端验证,这里拿RegisterViewModel举例 我们可以给限定属性是必须的 public class RegisterViewModel { [Required]//必须的 [DataType(DataType.EmailAddress)]//内容检查是否为邮箱 public string Email { get; set; } [Required]//必须的 [DataType(DataType.Password)]//内容检查是否为密码 public string Password { get; set; } [Required]//必须的 [DataType(DataType.Password)]//内容检查是否为密码 public string ConfirmedPassword { get; set; } } 这样之前我们在在登陆的时候也用的是RegisterViewModel就不行了,我们要在ViewModel文件夹下新建一个LoginViewModel供登陆使用 public class LoginViewModel { [Required]//必须的 [DataType(DataType.EmailAddress)]//内容检查是否为邮箱 public string Email { get; set; } [Required]//必须的 [DataType(DataType.Password)]//内容检查是否为密码 public string Password { get; set; } } 接下来我们需要修改Login.cshtml,在表单中添加<span asp-validation-for="XXXXXX" class="text-danger"></span>用来给表单元素显示错误信息 然后我们修改Login的HttpPost方法,用ModelState.IsValid进行验证 这时候我们什么数据都不填,服务端返回验证后显示: 同理Register方法也是这样进行修改 我们现在的密码验证很弱,是因为之前在Startup.cs中我们修改了密码的部分规则,现在将规则改为如下 我们可以将所有的错误提示在同一个地方,需要用asp-validation-summary,我们以Register.cshtml为例 什么都不填运行效果 由于我们的验证比较严格,会出现注册是失败的情况,所以我们要修改后台的注册方法,在注册失败的时候讲错误返回给前台页面,我们可以写一个通用的添加验证错误方法 //添加验证错误 private void AddError(IdentityResult result) { //遍历所有的验证错误 foreach (var error in result.Errors) { //返回error到model ModelState.AddModelError(string.Empty, error.Description); } } 然后在注册验证失败的时候调用此方法将错误原因显示出来 运行效果 Model前端验证 客户端的验证主要要加入jquery的组件 jquery.validate.js jquery.validate.unobtrusive.js 以Login.cshtml为例,我们只需要加入以下代码就行了 @section Scripts { @await Html.PartialAsync("_ValidationScriptsPartial") } 因为_Layout.cshtml中已经默认为我们加载了js
前言 之前我们进行了MVC的web页面的Cookie-based认证实现,接下来的开发我们要基于之前的MvcCookieAuthSample项目做修改。 MvcCookieAuthSample项目地址:http://www.cnblogs.com/wyt007/p/8128186.html UI 我们首先在AccountController中添加两个Action public IActionResult Register() { return View(); } public IActionResult Login() { return View(); } 然后在Views文件夹下新增Account文件夹并新增Register.cshtml与Login.cshtml视图,样式我们尽量从上一节的Identity视图中拷贝过来。 我们还需要新建一个ViewModels,在ViewModels中新建RegisterViewModel.cs来接收表单提交的值以及来进行强类型视图 namespace MvcCookieAuthSample.ViewModels { public class RegisterViewModel { //邮箱 public string Email { get; set; } //密码 public string Password { get; set; } //确认密码 public string ConfirmedPassword { get; set; } } } Register.cshtml代码(只保留部分拷贝过来的内容,并加入强类型视图引用): @{ ViewData["Title"] = "Register";} @using MvcCookieAuthSample.ViewModels;@model RegisterViewModel; <h2>@ViewData["Title"]</h2><h3>@ViewData["Message"]</h3> <div class="row"> <div class="col-md-4"> <form method="post"> <h4>Create a new account.</h4> <hr /> <div class="form-group"> <label asp-for="Email"></label> <input asp-for="Email" class="form-control" /> </div> <div class="form-group"> <label asp-for="Password"></label> <input asp-for="Password" class="form-control" /> </div> <div class="form-group"> <label asp-for="ConfirmedPassword"></label> <input asp-for="ConfirmedPassword" class="form-control" /> </div> <button type="submit" class="btn btn-default">Register</button> </form> </div></div> Login.cshtml代码(只保留部分拷贝过来的内容,并加入强类型视图引用): @{ ViewData["Title"] = "Login";} @using MvcCookieAuthSample.ViewModels;@model RegisterViewModel; <div class="row"> <div class="col-md-4"> <section> <form method="post"> <h4>Use a local account to log in.</h4> <hr /> <div class="form-group"> <label asp-for="Email"></label> <input asp-for="Email" class="form-control" /> </div> <div class="form-group"> <label asp-for="Password"></label> <input asp-for="Password" type="password" class="form-control" /> </div> <div class="form-group"> <button type="submit" class="btn btn-default">Log in</button> </div> </form> </section> </div></div> 然后在_Layout.cshtml中添加导航代码: <ul class="nav navbar-nav navbar-right"> <li><a asp-area="" asp-controller="Account" asp-action="Register">Register</a></li> <li><a asp-area="" asp-controller="Account" asp-action="Login">Log in</a></li> </ul> 然后运行网站,UI已经实现 EF + Identity实现 EF实现 首先我们添加一个Data文件夹,由于VSCode的代码提示不是很好,接下来我们用VS2017开发。 我们首先在Models文件夹下面新建ApplicationUser.cs与ApplicationUserRole.cs ApplicationUser.cs代码: using Microsoft.AspNetCore.Identity; namespace MvcCookieAuthSample.Models { public class ApplicationUser:IdentityUser<int>//不加int的话是默认主键为guid { } } ApplicationUserRole.cs代码: using Microsoft.AspNetCore.Identity; namespace MvcCookieAuthSample.Models { public class ApplicationUserRole: IdentityRole<int>//不加int的话是默认主键为guid { } } 然后在Data文件夹下新建一个ApplicationDbContext.cs类,使它继承IdentityDbContext using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using MvcCookieAuthSample.Models; namespace MvcCookieAuthSample.Data { public class ApplicationDbContext:IdentityDbContext<ApplicationUser, ApplicationUserRole,int> { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options):base(options) { } } } 然后我们需要在Startup.cs添加EF的注册进来 //使用配置ApplicationDbContext使用sqlserver数据库,并配置数据库连接字符串 services.AddDbContext<ApplicationDbContext>(options=> { options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")); }); 然后我们需要在appsettings.json中配置数据库连接字符串 "ConnectionStrings": { "DefaultConnection": "Server=192.168.1.184;Database=aspnet-IdentitySample-9A22BB3E-8D53-4F44-B533-2EF927C959DE;Trusted_Connection=True;MultipleActiveResultSets=true;uid=sa;pwd=123456" } EF实现结束 Identity实现 我们需要在Startup.cs添加Identity的注册进来 //配置Identity services.AddIdentity<ApplicationUser, ApplicationUserRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); 由于默认的Identity在密码上限制比较严格,我们把它改的宽松简单一点(不设置也行) //修改Identity配置 services.Configure<IdentityOptions>(options => { options.Password.RequireLowercase = false;//需要小写 options.Password.RequireNonAlphanumeric = false;//需要字母 options.Password.RequireUppercase = false;//需要大写 }); 然后我们把认证的地址改成/Account/Login 然后我们修改AccountController,加入以下代码 private UserManager<ApplicationUser> _userManager;//创建用户的 private SignInManager<ApplicationUser> _signInManager;//用来登录的 //依赖注入 public AccountController(UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager) { _userManager = userManager; _signInManager = signInManager; } [HttpPost] public async Task<IActionResult> Register(RegisterViewModel registerViewModel) { var identityUser = new ApplicationUser { Email = registerViewModel.Email, UserName = registerViewModel.Email, NormalizedUserName = registerViewModel.Email }; var identityResult=await _userManager.CreateAsync(identityUser, registerViewModel.Password); if (identityResult.Succeeded) { return RedirectToAction("Index", "Home"); } return View(); } 完整的AccountController using System;using System.Collections.Generic;using System.Diagnostics;using System.Linq;using System.Threading.Tasks;using Microsoft.AspNetCore.Mvc;using MvcCookieAuthSample.Models;using Microsoft.AspNetCore.Authorization;using Microsoft.AspNetCore.Authentication;using Microsoft.AspNetCore.Authentication.Cookies;using System.Security.Claims;using MvcCookieAuthSample.ViewModels;using Microsoft.AspNetCore.Identity; namespace MvcCookieAuthSample.Controllers{ public class AccountController : Controller { private UserManager<ApplicationUser> _userManager;//创建用户的 private SignInManager<ApplicationUser> _signInManager;//用来登录的 //依赖注入 public AccountController(UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager) { _userManager = userManager; _signInManager = signInManager; } public IActionResult Register() { return View(); } [HttpPost] public async Task<IActionResult> Register(RegisterViewModel registerViewModel) { var identityUser = new ApplicationUser { Email = registerViewModel.Email, UserName = registerViewModel.Email, NormalizedUserName = registerViewModel.Email }; var identityResult=await _userManager.CreateAsync(identityUser, registerViewModel.Password); if (identityResult.Succeeded) { return RedirectToAction("Index", "Home"); } return View(); } public IActionResult Login() { return View(); } //登陆 public IActionResult MakeLogin() { var claims=new List<Claim>(){ new Claim(ClaimTypes.Name,"wyt"), new Claim(ClaimTypes.Role,"admin") }; var claimIdentity= new ClaimsIdentity(claims,CookieAuthenticationDefaults.AuthenticationScheme); HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,new ClaimsPrincipal(claimIdentity)); return Ok(); } //登出 public IActionResult Logout() { HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); return Ok(); } }} 接下来我们重新生成一下,我们需要执行shell命令生成一下数据库,只有添加 Microsoft.EntityFrameworkCore.Tools 才会生成成功,否则会报以下错误 执行命令总是提示 未找到与命令“dotnet-ef”匹配的可执行文件,根据网上的解决办法引用 Microsoft.EntityFrameworkCore.Tools 问题依旧不能得到解决。 解决办法: 右击项目弹出菜单点击编辑***.csprog,增加如下配置。 <ItemGroup> <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" /> </ItemGroup> 执行增加配置命令后 这时候Data文件夹下已经有新增的数据库更新配置文件了 然后我们执行更新命令,执行成功后我们就可以看到数据库表已经生成了 接下来我们运行一下网站进行注册,注册成功,已经存储进数据库 注册实现 前面虽然可以注册了,但是我们注册完成后并没有生成Cookies信息。所以我们要在Register方法中进行登陆生成Cookies 完整的注册方法如下: [HttpPost]public async Task<IActionResult> Register(RegisterViewModel registerViewModel){ var identityUser = new ApplicationUser { Email = registerViewModel.Email, UserName = registerViewModel.Email, NormalizedUserName = registerViewModel.Email }; var identityResult=await _userManager.CreateAsync(identityUser, registerViewModel.Password); if (identityResult.Succeeded) { //注册完成登录生成cookies信息 await _signInManager.SignInAsync(identityUser, new AuthenticationProperties { IsPersistent = true }); return RedirectToAction("Index", "Home"); } return View();} 一般来说,如果用户已经注册或者登陆了,注册和登陆按钮是要隐藏的,所以我们接下来要修改_Layout.cshtml视图页面判断注册/登陆按钮是否应该隐藏 完整的_Layout.cshtml代码: <!DOCTYPE html><html><head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>@ViewData["Title"] - MvcCookieAuthSample</title> <environment include="Development"> <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" /> <link rel="stylesheet" href="~/css/site.css" /> </environment> <environment exclude="Development"> <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css" asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css" asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" /> <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" /> </environment></head><body> <nav class="navbar navbar-inverse navbar-fixed-top"> <div class="container"> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a asp-area="" asp-controller="Home" asp-action="Index" class="navbar-brand">MvcCookieAuthSample</a> </div> <div class="navbar-collapse collapse"> <ul class="nav navbar-nav"> <li><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li> <li><a asp-area="" asp-controller="Home" asp-action="About">About</a></li> <li><a asp-area="" asp-controller="Home" asp-action="Contact">Contact</a></li> </ul> @if (User.Identity.IsAuthenticated) { <form asp-action="Logout" asp-controller="Account" method="post"> <ul class="nav navbar-nav navbar-right"> <li> <a title="Welcome" asp-controller="Admin" asp-action="Index">@User.Identity.Name</a> </li> <li> <button type="submit" class="btn btn-link navbar-btn navbar-link">Log out</button> </li> </ul> </form> } else { <ul class="nav navbar-nav navbar-right"> <li><a asp-area="" asp-controller="Account" asp-action="Register">Register</a></li> <li><a asp-area="" asp-controller="Account" asp-action="Login">Log in</a></li> </ul> } </div> </div> </nav> <div class="container body-content"> @RenderBody() <hr /> <footer> <p>&copy; 2018 - MvcCookieAuthSample</p> </footer> </div> <environment include="Development"> <script src="~/lib/jquery/dist/jquery.js"></script> <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script> <script src="~/js/site.js" asp-append-version="true"></script> </environment> <environment exclude="Development"> <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js" asp-fallback-src="~/lib/jquery/dist/jquery.min.js" asp-fallback-test="window.jQuery" crossorigin="anonymous" integrity="sha384-K+ctZQ+LL8q6tP7I94W+qzQsfRV2a+AfHIi9k8z8l9ggpc8X+Ytst4yBo/hH+8Fk"> </script> <script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js" asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js" asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal" crossorigin="anonymous" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"> </script> <script src="~/js/site.min.js" asp-append-version="true"></script> </environment> @RenderSection("Scripts", required: false)</body></html> 这时候登陆的之后的导航栏信息就有了 登陆实现 我们接下来实现一下登陆逻辑,我们首先新建一个HttpPost的Login的Action [HttpPost] public async Task<IActionResult> Login(RegisterViewModel loginViewModel) { var user= await _userManager.FindByEmailAsync(loginViewModel.Email); if (user==null) { //异常先不写,后期统一收集 } //账号密码先不做验证,需要可以自己写 await _signInManager.SignInAsync(user, new AuthenticationProperties { IsPersistent = true }); return RedirectToAction("Index", "Home"); } 然后我们把原来的Logout也顺便修改一下,不能是return OK();而要跳转到首页 //登出 public async Task<IActionResult> Logout() { //HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); //return Ok(); await _signInManager.SignOutAsync(); return RedirectToAction("Index", "Home"); } 接下来我们修改一下Login.cshtml页面 @{ ViewData["Title"] = "Login";} @using MvcCookieAuthSample.ViewModels;@model RegisterViewModel; <div class="row"> <div class="col-md-4"> <section> <form method="post" asp-controller="Account" asp-action="Login"> <h4>Use a local account to log in.</h4> <hr /> <div class="form-group"> <label asp-for="Email"></label> <input asp-for="Email" class="form-control" /> </div> <div class="form-group"> <label asp-for="Password"></label> <input asp-for="Password" type="password" class="form-control" /> </div> <div class="form-group"> <button type="submit" class="btn btn-default">Log in</button> </div> </form> </section> </div></div> 接下来我们可以运行一下登陆注册,即可成功登陆后跳转到首页,注册后跳转倒是首页