微小黑开发笔记
一.说明
用c#基于.net 平台的MVC框架开发的微网站。
二.对象实体映射框架
1.Entity Framework:https://msdn.microsoft.com/en-us/library/jj592674(v=vs.113).aspx CodeFirst技术
1.1常用的数据库操作
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using (var context = new BloggingContext()) { var blog = new Blog { Name = "ADO.NET Blog" }; context.Entry(blog).State = EntityState.Added; context.SaveChanges(); }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
var existingBlog = new Blog { BlogId = 1, Name = "ADO.NET Blog" }; using (var context = new BloggingContext()) { context.Blogs.Attach(existingBlog); // Do some more work... context.SaveChanges(); }
1.2.创建数据库
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
[Serializable] [DataContract] [Table("WxStoreTB")] public class WxStoreTB : ModelBase { public WxStoreTB() { StoreBackground = "/Content/Images/adminbanner.jpg"; } [DataMember] public string DistributorSlogan { get; set; } [DataMember] public string StoreLogo { get; set; } [DataMember] public string StoreBackground { get; set; } [DataMember] public string IndexBackgroundMusic { get; set; } [DataMember] public string EnglishName { get; set; } [DataMember] public string QRCodeImage { get; set; } [DataMember] /// <summary> /// 店铺ID /// </summary> public int StoreTBID { get; set; } }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class DbContextBase : DbContext, IDataRepository, IDisposable { public DbContextBase(string connectionString) { this.Database.Connection.ConnectionString = connectionString; this.Configuration.LazyLoadingEnabled = false; this.Configuration.ProxyCreationEnabled = false; } public DbContextBase(string connectionString, IAuditable auditLogger) : this(connectionString) { this.AuditLogger = auditLogger; } public IAuditable AuditLogger { get; set; } public T Update<T>(T entity, bool isSave = true) where T : ModelBase { var set = this.Set<T>(); set.Attach(entity); this.Entry<T>(entity).State = EntityState.Modified; if (isSave) { this.SaveChanges(); } return entity; }
1.3对象关系映射:
1.3.1Data Annotations:缺点是污染域模型
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
[DataContract] [Table("PageTB")] public class PageTB : ModelBase { /// <summary> ///店铺ID /// </summary> [DataMember] [ForeignKey("StoreTB")] public int StoreTBID { get; set; } /// <summary> ///页面类别 0:首页 1:其他 /// </summary> [DataMember] public int PageType { get; set; } /// <summary> ///页面名称 /// </summary> [DataMember] public string GuidNumber { get; set; } /// <summary> ///页面路径(站点根目录为起始的相对路径) /// </summary> [DataMember] public string PagePath { get; set; } /// <summary> ///标题 /// </summary> [DataMember] public string Title { get; set; } /// <summary> /// 是否已上架 /// </summary> [DataMember] public bool IsOnShelf { get; set; } /// <summary> /// 版本号 空则放在商户目录下 /// </summary> [DataMember] public string Version { get; set; } }
1.3.2Fluent API:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
modelBuilder.Entity<BlogUser>().HasKey(user => user.UserId);
modelBuilder.Entity<BlogUser>().Property(user => user.BlogName).IsRequired();
三.使用面向对象操作数据库
1.Sql LinqSql LinqEntity
例如:查询Score表中至少有5名学生选修的并以3开头的课程的平均分数。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
select avg(degree) from score where cno like '3%' group by Cno having count(*)>=5
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from s in Scores where s.CNO.StartsWith("3") group s by s.CNO into cc where cc.Count() >= 5 select cc.Average( c => c.DEGREE)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
Scores.Where( s => s.CNO.StartsWith("3") ) .GroupBy( s => s.CNO ) .Where( cc => ( cc.Count() >= 5) ) .Select( cc => cc.Average( c => c.DEGREE) )
四.路由
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); //定时任务 JobManager.Initialize(new MyRegistry()); }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class MobileAreaRegistration : AreaRegistration { public override string AreaName { get { return "Mobile"; } } public override void RegisterArea(AreaRegistrationContext context) { context.MapRoute( "Mobile_default", "Mobile/{controller}/{action}/{id}", new { action = "Index", id = UrlParameter.Optional }, namespaces: new[] { "MiLai.Web.Admin.Areas.Mobile.Controllers" } ); } }
五.控制器 Controller
从controller获取客户端传值的方式
1. 从context object 直接提取
2. 通过参数传进来(由基类controller完成解析)
3. 通过model binding
context object常用的对象
Request.QueryString |
从Get 请求的url中取 |
Request.Form |
从Post请求的表单取 |
Request.Cookies |
把值放在请求的cookie里带过来 |
RouteData.Route |
从路由表里取注册的路由名 |
RouteData.Values |
从路由表获取路由配置的匿名对象 |
HttpContext.Cache |
应用程序缓存 |
HttpContext.Items |
存储一些值,只在同一请求中使用(可以在httppipeline过程的不同module,handler,以及页面传递值) |
HttpContext.Session |
当前用户的session |
常用的result:
ViewResult |
返回一个view,可以指定不同的controller |
ParcialViewResult |
返回部分view |
RedirectToActionResult |
转到另一个action |
RedirectResult |
返回301或者302(permanent) |
JsonResult |
返回js最喜欢的json,常用,尤其当前段打算采用纯js template,或者single page application |
FileResult |
文件result |
ContentResult |
字符串 |
HttpUnauthorizedResult |
401,通常,会自动跳转到登陆页面 |
HttpNotFoundResult |
404 |
六.Razor模板引擎(CSHTML)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
@{ ViewBag.Title = "Index"; Layout = "~/Views/Shared/_Layout.cshtml"; var request = ViewData["request"] as CourseRequest; var categoryList = ViewData["categoryList"] as List<int>; } @using Qxun.Framework.Contract @using Qxun.Framework.Utility @using Qxun.Framework.Web.Controls @using MiLai.Shop.Contract.Model @model PagedList<CourseTB> <div class="row picListBox" id="albumContainer"> @foreach (var item in Model) { <!--图片组件的边框--> <div class="picModuleBox" id="picbox_1"> <a data-fancybox-group="gallery" href="javascript:void 0;" onclick="videoplay('@item.MasterImage','@item.VedioPath')" title="a"> <img src="@item.MasterImage" /> </a> <div class="overLay"></div> <h1> <span class="info" style=""><a id="remark_1">@item.Remark</a></span></h1> <div class="checkBox"> <input type="checkbox" name='ids' value='@item.ID' /><span id="title_1" class="itemName">@item.Title</span> </div> <div class="functionBox"> <a class="" href="@Url.Action("Edit", new { id = item.ID })">编辑</a> <input id="1" type="text" class="InpSort" style=" width:35px; " value="11" /> </div> </div> } </div>
七.接入微信第三方
微信公众平台:https://mp.weixin.qq.com/advanced/selfmenu?action=index&t=advanced/menu-setting&token=2122708442&lang=zh_CN
微信测试公众平台:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
开发文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1445241432
1.基本配置
设置的服务器地址url要能正确的响应微信服务器发送过来的请求,参考:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1434696670
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public ActionResult Index(string signature, string timestamp, string nonce, string echostr,string encrypt_type, string msg_signature) { try { var token = CachedConfigContext.Current.WeixinConfig.Token; if (string.IsNullOrEmpty(token)) return Content("请先设置Token!"); var ent = ""; if (!BasicAPI.CheckSignature(signature, timestamp, nonce, token, out ent)) { LogHelper.WCInfo("CheckSignature:" + "CheckSignatureError"); return Content("参数错误!"); } LogHelper.WCInfo("验证通过"); var streamReader = new StreamReader(Request.InputStream); var msg = streamReader.ReadToEnd(); streamReader.Dispose(); LogHelper.WCInfo("msg:" + msg); var decryptMsg = string.Empty; SortedDictionary<string, object> result = new SortedDictionary<string, object>(); var wxBizMsgCrypt = new WXBizMsgCrypt( CachedConfigContext.Current.WeixinConfig.Token, CachedConfigContext.Current.WeixinConfig.EncodingAESKey, CachedConfigContext.Current.WeixinConfig.AppID); var ret = wxBizMsgCrypt.DecryptMsg(msg_signature, timestamp, nonce, msg, ref decryptMsg); LogHelper.WCInfo("ret:" + ret); if (ret == 0) { result = DataConvertHelper.FromXml(decryptMsg); foreach (var item in result) { LogHelper.WCInfo(string.Format("微信预授权方面的推送信息:key={0} => value={1}", item.Key, item.Value)); } string openID = (string)result["FromUserName"]; WxUserTB fromWxUser = ServiceContext.Current.HuiXianUserService.GetWxUser(u => u.OpenId == openID); if (result.ContainsKey("EventKey") && fromWxUser != null) { int menuID = 0; try { menuID = Convert.ToInt32(result["EventKey"]); } catch (Exception e) { LogHelper.WCInfo(e.Message); } if (menuID > 0) { var setting = ServiceContext.Current.HuiXianCmsService.GetWxSetting(); WeixinMenuTB weixinMenu = ServiceContext.Current.WeixinService.GetWeixinMenu(menuID); if (weixinMenu != null) { if (weixinMenu.MaterialID > 0) { WeixinMaterialTB weixinMaterial = ServiceContext.Current.WeixinMaterialService.GetMaterial(weixinMenu.MaterialID); if (weixinMaterial != null) { if (!string.IsNullOrEmpty(weixinMaterial.MediaId)) { WeixinMaterialSubTB weixinMaterialSub = ServiceContext.Current.WeixinMaterialService.GetFirstMaterialSub(weixinMaterial.ID); if (weixinMenu.Type == "click") { //微信菜单点击后被动回复图文消息 WeixinNews news = new WeixinNews { picurl =CachedConfigContext.Current.WeixinConfig.Domain+ weixinMaterialSub.ImageUrl, title = weixinMaterialSub.Title, description = weixinMaterialSub.Content, url = weixinMaterialSub.Url }; string repayResult = ReplayPassiveMessageAPI.RepayNews(fromWxUser.OpenId, "gh_117ef9c5cdee", news); LogHelper.WCInfo("repayResult:" + repayResult); return Content(repayResult); } } else { LogHelper.WCInfo("微信素材尚未上传至微信服务器"); } } else { LogHelper.WCInfo("微信素材不存在"); } } else { //如果菜单未关联素材 默认就发微信二维码 if (string.IsNullOrEmpty(fromWxUser.media_id)) { fromWxUser.QRCode= ServiceContext.Current.HuiXianUserService.GetWxUserQRCode(fromWxUser); LogHelper.WCInfo("QRCode:" + fromWxUser.QRCode); } string imgurl = CachedConfigContext.Current.WeixinConfig.Domain + fromWxUser.QRCode; WebRequest request = HttpWebRequest.Create(imgurl); var response = request.GetResponse(); Stream stream = response.GetResponseStream(); int pos = imgurl.LastIndexOf("/"); string fileName = imgurl.Substring(pos + 1); LogHelper.WCInfo("fileName:" + fileName); ResultData ResultData = MaterialAPI.AddMaterial(setting.AccessToken, "image", fileName, stream); LogHelper.WCInfo("ResultData:" + JsonConvert.SerializeObject(ResultData)); string media_id = (string)ResultData.media_id; LogHelper.WCInfo("media_id:" + media_id); if (!string.IsNullOrEmpty(media_id)) { fromWxUser.media_id = media_id; ServiceContext.Current.HuiXianUserService.UpdateWxUser(fromWxUser); string repayResult = ReplayPassiveMessageAPI.ReplayImage(fromWxUser.OpenId, "gh_117ef9c5cdee", media_id); return Content(repayResult); } } } else { LogHelper.WCInfo("微信菜单不存在"); } } else { LogHelper.WCInfo("菜单不存在"); } } } LogHelper.WCInfo("END"); } catch (Exception e) { LogHelper.WCInfo(e.Message); } return Content(echostr); //返回随机字符串则表示验证通过 }
1.获取access_token(调用接口的令牌) https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183
1.1 第三方程序集 定时任务执行器 FluentScheduler :https://github.com/fluentscheduler/FluentScheduler
1.2调用接口获取access_token
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public static dynamic GetAccessToken(string appid, string secrect) { var url = string.Format("https://api.weixin.qq.com/cgi-bin/token?grant_type={0}&appid={1}&secret={2}", "client_credential", appid, secrect); var client = new HttpClient(); var result = client.GetAsync(url).Result; if (!result.IsSuccessStatusCode) return string.Empty; var token = DynamicJson.Parse(result.Content.ReadAsStringAsync().Result); return token; }
2.用户自定义菜单 https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141013
3.新增素材 https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1494572718_WzHIY
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public static ResultData AddNews(string access_token, List<WeixinArtcle> articles) { var url = string.Format("https://api.weixin.qq.com/cgi-bin/material/add_news?access_token={0}", access_token); var client = new HttpClient(); var atrticle = DynamicJson.Serialize(articles); var builder = new StringBuilder(); builder .Append("{") .Append('"' + "articles" + '"' + ":").Append(atrticle) .Append("}"); var result = client.PostAsync(url, new StringContent(builder.ToString())).Result; if (!result.IsSuccessStatusCode) return null; else { return DynamicJson.Parse(result.Content.ReadAsStringAsync().Result); } }
4.模板消息 https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751277
5.网页授权 https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public ActionResult OAuth(string state) { this.WxUserCookie = ""; var domain = CachedConfigContext.Current.WeixinConfig.Domain; var appId = CachedConfigContext.Current.WeixinConfig.AppID; var redirect_uri = System.Web.HttpUtility.UrlEncode(string.Format("{0}/Mobile/WCUser/Callback", domain)); LogHelper.WCInfo(string.Format("微信授权redirect_uri:{0}/Mobile/WCUser/Callback", domain)); var weixinOAuth2Url = string.Format( "https://open.weixin.qq.com/connect/oauth2/authorize?appid={0}&redirect_uri={1}&response_type=code&scope={2}&state={3}#wechat_redirect", appId, redirect_uri, "snsapi_userinfo", state); LogHelper.WCInfo("微信授权weixinOAuth2Url:" + weixinOAuth2Url); return Redirect(weixinOAuth2Url); }
5.1网页授权回调
用户同意授权后页面将跳转至 redirect_uri/?code=CODE&state=STATE
5.2获取网页授权access_token
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public static dynamic GetAccessToken(string code, string appId, string appSecret) { var client = new HttpClient(); var result = client.GetAsync(string.Format("https://api.weixin.qq.com/sns/oauth2/access_token?appid={0}&secret={1}&code={2}&grant_type=authorization_code", appId, appSecret, code)).Result; if (!result.IsSuccessStatusCode) return null; return DynamicJson.Parse(result.Content.ReadAsStringAsync().Result); }
5.3拉取用户信息
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public static dynamic GetUserInfo(string accessToekn, string openId, string lang = "zh_CN") { var client = new HttpClient(); var result = client.GetAsync(string.Format("https://api.weixin.qq.com/sns/userinfo?access_token={0}&openid={1}&lang={2}", accessToekn, openId, lang)).Result; if (!result.IsSuccessStatusCode) return null; return DynamicJson.Parse(result.Content.ReadAsStringAsync().Result); }
6.JS-SDK https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115
6.1获取jsapi_ticket
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public static dynamic GetTickect(string access_token) { var url = string.Format("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={0}&type=jsapi", access_token); var client = new HttpClient(); var result = client.GetAsync(url).Result; if (!result.IsSuccessStatusCode) return string.Empty; var jsTicket = DynamicJson.Parse(result.Content.ReadAsStringAsync().Result); return jsTicket; }
6.2签名算法
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public static string GetSignature(string jsapi_ticket, string noncestr, long timestamp, string url, out string string1)
{
var string1Builder = new StringBuilder();
string1Builder.Append("jsapi_ticket=").Append(jsapi_ticket).Append("&")
.Append("noncestr=").Append(noncestr).Append("&")
.Append("timestamp=").Append(timestamp).Append("&")
.Append("url=").Append(url.IndexOf("#") >= 0 ? url.Substring(0, url.IndexOf("#")) : url);
string1 = string1Builder.ToString();
return Util.Sha1(string1);
public static string Sha1(string orgStr, string encode = "UTF-8")
{
var sha1 = new SHA1Managed();
var sha1bytes = System.Text.Encoding.GetEncoding(encode).GetBytes(orgStr);
byte[] resultHash = sha1.ComputeHash(sha1bytes);
string sha1String = BitConverter.ToString(resultHash).ToLower();
sha1String = sha1String.Replace("-", "");
return sha1String;
}
6.3创建JSSDK对象
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public static JSSDKModel GetJsSdk(string ticket,string appID,string requestUrl,string shareUrl,string shareImg,string title,string shareDesc)
{
var nonceStr = Guid.NewGuid().ToString("N");
string message = "";
var timeStamp = DateTimeHelper.DateTimeToUnixTimestamp(DateTime.Now);
var signature = JSAPI.GetSignature(ticket, nonceStr, timeStamp, requestUrl, out message);
var model = new JSSDKModel()
{
appId = appID,
nonceStr = nonceStr,
signature = signature,
timestamp = timeStamp,
jsapiTicket = ticket,
shareUrl =shareUrl,
shareImg = shareImg,
title = title,
desc = shareDesc
};
return model;
}
6.4嵌入js脚本
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js" type="text/javascript"></script> <script> (function(){ wx.config({ debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 appId: '', // 必填,公众号的唯一标识 timestamp: , // 必填,生成签名的时间戳 nonceStr: '', // 必填,生成签名的随机串 signature: '',// 必填,签名,见附录1 jsApiList: [] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2 }); wx.ready(function(){ execute(); // config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。 }); })() function execute() { var title = "@ViewBag.JSSDK.title"; // 分享标题 var link = '@Server.UrlDecode(ViewBag.JSSDK.shareUrl)'; // 分享链接 var imgUrl = '@ViewBag.JSSDK.shareImg'; // 分享图标 var desc = "@ViewBag.JSSDK.desc"; // 分享描述 wxJs.showmenu(); //朋友圈 wxJs.onMenuShareAppMessage({ title: title, link: link, imgUrl: imgUrl, desc: desc, ok: function () { //分享成功后,增加分享记录 jsprint("分享成功", "", "success"); }, cancel: function () { jsprint("分享取消", "", "error"); } }); //转发给朋友的 wxJs.onMenuShareTimeline({ title: title, link: link, imgUrl: imgUrl, ok: function () { jsprint("分享成功", "", "success"); }, cancel: function () { jsprint("分享取消", "", "error"); } }); //var latitude = 0; //var longitude = 0; //wx.getLocation({ // success: function (res) { // //获取经纬度数值 按照,分割字符串 取出前两位 解析成浮点数 // latitude=res.latitude; // longitude=res.longitude; // }, // cancel: function (res) { // alert('用户拒绝授权获取地理位置'); // } //}); //wx.openLocation({ // latitude: latitude, // longitude: longitude, // name: '测试地址', // address: '广州市海珠区新港中路 397 号', // scale: 14, // infoUrl: 'http://weixin.qq.com' //}); }) </script>
7.微信支付 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1