1. 血案由来
近期我在为Lazada卖家中心做一个自助注册的项目,其中的shop name校验规则较为复杂,要求:
1. 英文字母大小写
2. 数字
3. 越南文
4. 一些特殊字符,如“&”,“-”,“_”等
看到这个要求的时候,自然而然地想到了正则表达式。于是就有了下面的表达式(写的比较龊):
- ^([A-Za-z0-9._()&'\- ]|[aAàÀảẢãÃáÁạẠăĂằẰẳẲẵẴắẮặẶâÂầẦẩẨẫẪấẤậẬbBcCdDđĐeEèÈẻẺẽẼéÉẹẸêÊềỀểỂễỄếẾệỆfFgGhHiIìÌỉỈĩĨíÍịỊjJkKlLmMnNoOòÒỏỎõÕóÓọỌôÔồỒổỔỗỖốỐộỘơƠờỜởỞỡỠớỚợỢpPqQrRsStTuUùÙủỦũŨúÚụỤưƯừỪửỬữỮứỨựỰvVwWxXyYỳỲỷỶỹỸýÝỵỴzZ])+$
在测试环境,这个表达式从功能上符合业务方的要求,就被发布到了马来西亚的线上环境。结果上线之后,发现线上机器时有发生CPU飙到100%的情况,导致整个站点响应异常缓慢。通过dump线程trace,才发现线程全部卡在了这个正则表达式的校验上:
一开始难以置信,一个正则表达式的匹配过程怎么可能引发CPU飚高呢?抱着怀疑的态度去查了资料才发现小小的正则表达式里面竟然大有文章,平时写起来都是浅尝辄止,只要能够满足功能需求,就认为达到目的了,完全忽略了它可能带来的性能隐患。
引发这次血案的就是所谓的正则“回溯陷阱(Catastrophic Backtracking)”。下面详细介绍下这个问题,以避免重蹈覆辙。
2. 正则表达式引擎
说起回溯陷阱,要先从正则表达式的引擎说起。正则引擎主要可以分为基本不同的两大类:一种是DFA(确定型有穷自动机),另一种是NFA(不确定型有穷自动机)。简单来讲,NFA 对应的是正则表达式主导的匹配,而 DFA 对应的是文本主导的匹配。
DFA从匹配文本入手,从左到右,每个字符不会匹配两次,它的时间复杂度是多项式的,所以通常情况下,它的速度更快,但支持的特性很少,不支持捕获组、各种引用等等;而NFA则是从正则表达式入手,不断读入字符,尝试是否匹配当前正则,不匹配则吐出字符重新尝试,通常它的速度比较慢,最优时间复杂度为多项式的,最差情况为指数级的。但NFA支持更多的特性,因而绝大多数编程场景下(包括java,js),我们面对的是NFA。以下面的表达式和文本为例,
- text = ‘after tonight’ regex = ‘to(nite|nighta|night)’
在NFA匹配时候,是根据正则表达式来匹配文本的,从t开始匹配a,失败,继续,直到文本里面的第一个t,接着比较o和e,失败,正则回退到 t,继续,直到文本里面的第二个t,然后 o和文本里面的o也匹配,继续,正则表达式后面有三个可选条件,依次匹配,第一个失败,接着二、三,直到匹配。
而在DFA匹配时候,采用的是用文本来匹配正则表达式的方式,从a开始匹配t,直到第一个t跟正则的t匹配,但e跟o匹配失败,继续,直到文本里面的第二个 t 匹配正则的t,接着o与o匹配,n的时候发现正则里面有三个可选匹配,开始并行匹配,直到文本中的g使得第一个可选条件不匹配,继续,直到最后匹配。
可以看到,DFA匹配过程中文本中的字符每一个只比较了一次,没有吐出的操作,应该是快于NFA的。另外,不管正则表达式怎么写,对于DFA而言,文本的匹配过程是一致的,都是对文本的字符依次从左到右进行匹配,所以,DFA在匹配过程中是跟正则表达式无关的,而 NFA 对于不同但效果相同的正则表达式,匹配过程是完全不同的。
3. 回溯
说完了引擎,我们再来看看到底什么是回溯。对于下面这个表达式,相信大家很清楚它的意图,
- ab{1,3}c
也就是说中间的b需要匹配1~3次。那么对于文本“abbbc”,按照第1部分NFA引擎的匹配规则,其实是没有发生回溯的,在表达式中的a匹配完成之后,b恰好和文本中的3个b完整匹配,之后是c发生匹配,一气呵成。如果我们把文本换成“abc”呢?无非就是少了一个字母b,却发生了所谓的回溯。匹配过程如下图所示(橙色为匹配,黄色为不匹配),
1~2步应该都好理解,但是为什么在第3步开始,虽然已经文本中已经有一个b匹配了b{1,3},后面还会拉着字母c跟b{1,3}做比较呢?这个就是我们下面将要提到的正则的贪婪特性,也就是说b{1,3}会竭尽所能的匹配最多的字符。在这个地方我们先知道它一直要匹配到撞上南墙为止。 在这种情况下,第3步发生不匹配之后,整个匹配流程并没有走完,而是像栈一样,将字符c吐出来,然后去用正则表达式中的c去和文本中的c进行匹配。这样就发生了一次回溯。
4. 贪婪、懒惰与独占
我们再来看一下究竟什么是贪婪模式。
下面的几个特殊字符相信大家都知道它们的用法:
i. ?: 告诉引擎匹配前导字符0次或一次。事实上是表示前导字符是可选的。
ii. +: 告诉引擎匹配前导字符1次或多次。
iii. *: 告诉引擎匹配前导字符0次或多次。
iv. {min, max}: 告诉引擎匹配前导字符min次到max次。min和max都是非负整数。如果有逗号而max被省略了,则表示max没有限制;如果逗号和max都被省略了,则表示重复min次。
默认情况下,这个几个特殊字符都是贪婪的,也就是说,它会根据前导字符去匹配尽可能多的内容。这也就解释了为什么在第3部分的例子中,第3步以后的事情会发生了。
在以上字符后加上一个问号(?)则可以开启懒惰模式,在该模式下,正则引擎尽可能少的重复匹配字符,匹配成功之后它会继续匹配剩余的字符串。在上例中,如果将正则换为
- ab{1,3}?c
则匹配过程变成了下面这样(橙色为匹配,黄色为不匹配),
由此可见,在非贪婪模式下,第2步正则中的b{1,3}?与文本b匹配之后,接着去用c与文本中的c进行匹配,而未发生回溯。
如果在以上四种表达式后加上一个加号(+),则会开启独占模式。同贪婪模式一样,独占模式一样会匹配最长。不过在独占模式下,正则表达式尽可能长地去匹配字符串,一旦匹配不成功就会结束匹配而不会回溯。我们以下面的表达式为例,
- ab{1,3}+bc
如果我们用文本"abbc"去匹配上面的表达式,匹配的过程如下图所示(橙色为匹配,黄色为不匹配),
可以发现,在第2和第3步,b{1,3}+会将文本中的2个字母b都匹配上,结果文本中只剩下一个字母c。那么在第4步时,正则中的b和文本中的c进行匹配,当无法匹配时,并不进行回溯,这时候整个文本就无法和正则表达式发生匹配。如果将正则表达式中的加号(+)去掉,那么这个文本整体就是匹配的了。
把以上三种模式的表达式列出如下,
贪婪 |
懒惰 |
独占 |
X? |
X?? |
X?+ |
X* |
X*? |
X*+ |
X+ |
X+? |
X++ |
X{n} |
X{n}? |
X{n}+ |
X{n,} |
X{n,}? |
X{n,}+ |
X{n,m} |
X{n,m}? |
X{n,m}+ |
5. 总结
现在再回过头看看文章开头的那个很长的正则表达式,其实简化之后,就是一个形如
- ^[允许字符集]+
的表达式。该字符集大小约为250,而+号表示至少出现一次。按照上面说到的NFA引擎贪婪模式,在用户输入一个过长字符串进行匹配时,一旦发生回溯,计算量将是巨大的。后来采用了独占模式,CPU 100%的问题也得到了解决。
因此,在自己写正则表达式的时候,一定不能大意,在实现功能的情况下,还要仔细考虑是否会带来性能隐患。
关于正则表达式,你有哪些想要分享的特殊技能?欢迎在下面留言,一起交流探讨。
vs2017使用rdlc实现批量打印
接着上一篇:上一篇写了安装,这篇直接搞定批量打印,A4纸横版竖版页面设计,正式开始。(我的表达不怎么好,我尽量发图片都是程序员一点就通)
一、界面展示
忽略界面设计丑
查看预览界面,因为有数据就不截全屏了,盒号是我自己加的,我们自己的业务逻辑。
三、核心代码,批量打印(参考代码链接,放到文章结尾处)
1 public class BillPrint : IDisposable 2 { 3 ///4 /// 当前打印页号 5 /// 6 static int m_currentPageIndex; 7 8 ///9 /// RDCL转换stream一页对应一个stream 10 /// 11 static Listm_streams; 12 13 /// 14 /// 把report输出成stream 15 /// 16 /// 传入需要Export的report 17 private void Export(LocalReport report) 18 { 19 string deviceInfo = 20 "" + 21 " "; 29 m_streams = new ListEMF " + 22 //"2in " + 23 //"20in " + 24 //"0in " + 25 //"0in " + 26 //"0in " + 27 //"0in " + 28 "(); 30 report.Render("Image", deviceInfo, CreateStream, out Warning[] warnings); 31 foreach (Stream stream in m_streams) 32 stream.Position = 0; 33 } 34 35 /// 36 /// 创建具有指定的名称和格式的流。 37 /// 38 private Stream CreateStream(string name, string fileNameExtension, Encoding encoding, string mimeType, bool willSeek) 39 { 40 Stream stream = new FileStream(name + "." + fileNameExtension, 41 FileMode.Create); 42 m_streams.Add(stream); 43 return stream; 44 } 45 46 ///47 /// 打印输出 48 /// 49 private void PrintPage(object sender, PrintPageEventArgs ev) 50 { 51 Metafile pageImage = 52 new Metafile(m_streams[m_currentPageIndex]); 53 ev.Graphics.DrawImage(pageImage, ev.PageBounds); 54 m_currentPageIndex++; 55 ev.HasMorePages = (m_currentPageIndex < m_streams.Count); 56 } 57 ///58 /// 设置横版打印 59 /// 60 /// 61 /// 62 void Document_QueryPageSettings(object sender, QueryPageSettingsEventArgs e) 63 { 64 e.PageSettings.Landscape = false; 65 int index = -1; 66 for (int i = 0; i < e.PageSettings.PrinterSettings.PaperSizes.Count; i++) 67 { 68 if (e.PageSettings.PrinterSettings.PaperSizes[i].PaperName == "A4") 69 { 70 index = i; 71 break; 72 } 73 } 74 if (index != -1) 75 { 76 e.PageSettings.PaperSize = e.PageSettings.PrinterSettings.PaperSizes[index]; 77 } 78 } 79 80 81 ///82 /// 打印预处理 83 /// 84 private void Print(string printerName = null) 85 { 86 PrintDocument printDoc = new PrintDocument(); 87 if (string.IsNullOrEmpty(printerName)) 88 { 89 printerName = printDoc.PrinterSettings.PrinterName; 90 } 91 if (m_streams == null || m_streams.Count == 0) 92 return; 93 printDoc.PrinterSettings.PrinterName = printerName; 94 if (!printDoc.PrinterSettings.IsValid) 95 { 96 string msg = String.Format("Can't find printer \"{0}\".", printerName); 97 throw new Exception(msg); 98 } 99 printDoc.PrintPage += new PrintPageEventHandler(PrintPage); 100 101 //设置横版打印 102 printDoc.QueryPageSettings += new QueryPageSettingsEventHandler(Document_QueryPageSettings); 103 104 StandardPrintController spc = new StandardPrintController(); 105 printDoc.PrintController = spc; 106 printDoc.Print(); 107 } 108 109 ///110 /// 对外接口,启动打印 111 /// 112 /// 113 /// 默认打印机 114 public static void Run(LocalReport report, string printerName = null) 115 { 116 m_currentPageIndex = 0; 117 BillPrint billPrint = new BillPrint(); 118 billPrint.Export(report); 119 billPrint.Print(); 120 billPrint.Dispose(); 121 } 122 123 124 ///125 /// 获取打印机状态 126 /// 127 /// 打印机名称 128 /// 输出打印机状态 129 private static void GetPrinterStatus2(string printerName, ref uint status) 130 { 131 try 132 { 133 string lcPrinterName = printerName; 134 IntPtr liHandle = IntPtr.Zero; 135 if (!Win32.OpenPrinter(lcPrinterName, out liHandle, IntPtr.Zero)) 136 { 137 Console.WriteLine("print is close"); 138 return; 139 } 140 UInt32 level = 2; 141 IntPtr buffer = IntPtr.Zero; 142 Win32.GetPrinter(liHandle, level, buffer, 0, out uint sizeNeeded); 143 buffer = Marshal.AllocHGlobal((int)sizeNeeded); 144 if (!Win32.GetPrinter(liHandle, level, buffer, sizeNeeded, out sizeNeeded)) 145 { 146 Console.WriteLine(Environment.NewLine + "Fail GetPrinter:" + Marshal.GetLastWin32Error()); 147 return; 148 } 149 150 Win32.PRINTER_INFO_2 info = (Win32.PRINTER_INFO_2)Marshal.PtrToStructure(buffer, typeof(Win32.PRINTER_INFO_2)); 151 status = info.Status; 152 Marshal.FreeHGlobal(buffer); 153 Win32.ClosePrinter(liHandle); 154 } 155 catch (Exception ex) 156 { 157 throw ex; 158 } 159 } 160 161 ///162 /// 对外接口,调去打印机信息 163 /// 164 /// 打印机名称 165 ///返回打印机当前状态 166 public static string GetPrinterStatus(string printerName) 167 { 168 uint intValue = 0; 169 PrintDocument pd = new PrintDocument(); 170 printerName = printerName == "" ? pd.PrinterSettings.PrinterName : printerName; 171 GetPrinterStatus2(printerName, ref intValue); 172 string strRet = string.Empty; 173 switch (intValue) 174 { 175 case 0: 176 strRet = "准备就绪(Ready)"; 177 break; 178 case 4194432: 179 strRet = "被打开(Lid Open)"; 180 break; 181 case 144: 182 strRet = "打印纸用完(Out of Paper)"; 183 break; 184 case 4194448: 185 strRet = "被打开并且打印纸用完(Out of Paper && Lid Open)"; 186 break; 187 case 1024: 188 strRet = "打印中(Printing)"; 189 break; 190 case 32768: 191 strRet = "初始化(Initializing)"; 192 break; 193 case 160: 194 strRet = "手工送纸(Manual Feed in Progress)"; 195 break; 196 case 4096: 197 strRet = "脱机(Offline)"; 198 break; 199 default: 200 strRet = "未知状态(unknown state)"; 201 break; 202 } 203 return strRet; 204 } 205 206 207 public void Dispose() 208 { 209 if (m_streams != null) 210 { 211 foreach (Stream stream in m_streams) 212 stream.Close(); 213 m_streams = null; 214 } 215 } 216 } 217 218 public class Win32 219 { 220 [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)] 221 public static extern bool OpenPrinter(string printer, out IntPtr handle, IntPtr printerDefaults); 222 [DllImport("winspool.drv")] 223 public static extern bool ClosePrinter(IntPtr handle); 224 [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)] 225 public static extern bool GetPrinter(IntPtr handle, UInt32 level, IntPtr buffer, UInt32 size, out UInt32 sizeNeeded); 226 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] 227 public struct PRINTER_INFO_2 228 { 229 public string pServerName; 230 public string pPrinterName; 231 public string pShareName; 232 public string pPortName; 233 public string pDriverName; 234 public string pComment; 235 public string pLocation; 236 public IntPtr pDevMode; 237 public string pSepFile; 238 public string pPrintProcessor; 239 public string pDatatype; 240 public string pParameters; 241 public IntPtr pSecurityDescriptor; 242 public UInt32 Attributes; 243 public UInt32 Priority; 244 public UInt32 DefaultPriority; 245 public UInt32 StartTime; 246 public UInt32 UntilTime; 247 public UInt32 Status; 248 public UInt32 cJobs; 249 public UInt32 AveragePPM; 250 } 251 }
代码使用
1 using (ReportViewer rvDoc = new ReportViewer()) 2 { 3 rvDoc.LocalReport.DataSources.Add(new ReportDataSource("DataSet1", 你的数据)); 4 //注意自己的路径,这块我写到配置文件,来区分测试跟线上路径。 5 if (PublicProperty.RdlcPath == "Debug") 6 { 7 rvDoc.LocalReport.ReportPath = @"..\..\ReportForm\GTTKWenShuArchives.rdlc"; 8 } 9 else 10 { 11 rvDoc.LocalReport.ReportPath = Application.StartupPath + "\\ReportForm\\GTTKWenShuArchives.rdlc"; 12 } 13 //开始打印,第二个参数是选择打印机名称 14 BillPrint.Run(rvDoc.LocalReport, (string)printerList.Invoke(new obj_delegate(() => { return printerList.SelectedItem.ToString(); }))); 15 } 16 }
四、设计报表一些注意事项(可以用差之毫厘失之千里来形容)
- A4竖版打印,标头设计宽,长只能小于等于宽,要是大于就会出现空白页情况。
- A4横版打印,标头设计宽,长度跟竖版一样,注意这个数字是我一点点试出来的,多一点就会出现表的列显示不全,会跑到第二页里面,大家也可以自己试试。
- 要想每一页都显示标题,只能把标题加入到页眉之中,注意页眉的底部一定要跟表重合否则到第二页跟上边距会跟第一页不一样,具体什么样自己试一下就知道了,
- 表要是想加实线,注意设计的时候,这个大家一试便知。
- 要想每一页都显示表的标题部分可以这么设计
-
剩下的内容的字体啊间距啊,就根据自己需求自己调吧,注意设计的时候尽量表要与两边重合,标题要与顶部重合,因为他默认是上下左右间距都是2CM,你要是有距离你打印出来就不好看了,这个自己试试就知道了。
五、结尾
把一些我参考的链接放出来,大家可以自行参考。
https://blog.csdn.net/nuptsv_ice/article/details/41821611
有个批量打印代码链接找不到了,要是找到会补上去的。
vs2017使用rdlc
写在前面:因为公司要求做个批量打印工具,以前用Delphi+FastReport开发的,现在因为公司就剩下一个Delphi开发工程师了,还外出,所以这是就落在我身上。因为这个打印工具不需要使用人员设计,只要个模板打印就行, 我这用的工具是vs2017+winfrom+rdlc,好像FastReport收费了。
一、vs2017配置rdlc
因为vs2017默认没有装报表的需要自行安装,安装方法工具>扩展和更新>联机>搜索rdlc默认第一个安装即可,可能有点慢,我的安装的很长时间,你也可以自己去单独下载。安装完成后重启vs2017就有了。
二、生成项目
默认有个向导,可根据自己需求添加,也可以取消,后续自己添加。
项目结构
修改属性
三、设计报表,生成数据
下面设计一个打印界面,上面向导的时候我们把添加数据集跳过了,现在我们自行添加数据,
这里我们自定义列,也可以从数据库获取
然后添加数据集
到我们winfrom界面添加报表
后台数据绑定
点击启动即可看到以下界面,对文档的字体大小都可以通过设计页面进行设计
三、结尾
基本使用到此结束,剩下的就自行扩展了,至于批量打印功能,我这还没有申请到打印机,等后续打印机到手,进行测试之后补上。
[asp.net core 源码分析] 01 - Session
1、Session文档介绍
- 毋庸置疑学习.Net core最好的方法之一就是学习微软.Net core的官方文档;https://docs.microsoft.com/zh-cn/aspnet/core;
- .Net core Session的官方文档 https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/app-state
- .Net core Session Github源码 https://github.com/aspnet/Session
2、Session简单应用
2.1、在Startup类的ConfigureServices方法中添加
services.AddDistributedMemoryCache(); services.AddSession();
因为Session的服务端存储需要缓存,所以需要引入.Net core的缓存DistributedMemoryCache;
2.2、在Startup类的Configure方法中添加
app.UseSession();
2.3、使用(存储和获取)
// 存储 HttpContext.Session.Set("LoginId", System.Text.Encoding.Default.GetBytes("666")); // 获取 HttpContext.Session.TryGetValue("LoginId", out byte[] byteLoginId); var loginId = System.Text.Encoding.Default.GetString(byteLoginId); // LoginId="666";
3、源码分析图
4、源码分析
4.1、程序加载
4.1.1、在ConfigureServices中添加分布式缓存,services.AddDistributedMemoryCache();
微软官方建议使用AddDistributedMemoryCache,当然也可以使用AddMemoryCache、AddDistributedRedisCache、AddDistributedSqlServerCache或者自定义缓存也是可以的;
如果是分布式系统或者SSO单点登录,建议使用分布式的缓存,不要使用AddMemoryCache;
缓存的官方文档 https://docs.microsoft.com/zh-cn/aspnet/core/performance/caching/memory
4.1.2、在ConfigureServices中添加AddSession;
1 public static IServiceCollection AddSession(this IServiceCollection services) 2 { 3 if (services == null) 4 { 5 throw new ArgumentNullException(nameof(services)); 6 } 7 8 services.AddTransient(); 9 services.AddDataProtection(); 10 return services; 11 } 12 13 public static IServiceCollection AddSession(this IServiceCollection services, Action configure) 14 { 15 if (services == null) 16 { 17 throw new ArgumentNullException(nameof(services)); 18 } 19 20 if (configure == null) 21 { 22 throw new ArgumentNullException(nameof(configure)); 23 } 24 25 services.Configure(configure); 26 services.AddSession(); 27 28 return services; 29 }
AddSession为IServiceCollection的扩展方法,有1个重载(传入Session的设置,使用services.Configure(configure),加载设置);
services.AddDataProtection()注入数据加密解密DataProtection(),在加密解密SessionKey时使用;
services.AddTransient
4.1.3、在Configure中UseSession
1 public static IApplicationBuilder UseSession(this IApplicationBuilder app) 2 { 3 if (app == null) 4 { 5 throw new ArgumentNullException(nameof(app)); 6 } 7 8 return app.UseMiddleware(); 9 } 10 11 12 public static IApplicationBuilder UseSession(this IApplicationBuilder app, SessionOptions options) 13 { 14 if (app == null) 15 { 16 throw new ArgumentNullException(nameof(app)); 17 } 18 if (options == null) 19 { 20 throw new ArgumentNullException(nameof(options)); 21 } 22 23 return app.UseMiddleware (Options.Create(options)); 24 }
UseSession为IApplicationBuilder的扩展方法,也有1个重载,同样也是加载Session的设置,使用Options.Create(options)结合中间件加载设置;
关于中间件可以参考文档 https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware
SessionMiddleware.cs为Session的中间件;其中包含Session的核心代码,操作MVC之前和之后的代码都在中间件中;
4.2、SessionMiddleware.cs类解析
在SessionMiddleware中一个异步方法Invoke;主要逻辑中包含了注释,应该很好理解;
1 ///2 /// Invokes the logic of the middleware. 3 /// 4 /// The. 5 /// A 6 public async Task Invoke(HttpContext context) 7 { 8 var isNewSessionKey = false; 9 Functhat completes when the middleware has completed processing. tryEstablishSession = ReturnTrue; 10 var cookieValue = context.Request.Cookies[_options.Cookie.Name]; 11 12 // 解密cookieValue 13 var sessionKey = CookieProtection.Unprotect(_dataProtector, cookieValue, _logger); 14 if (string.IsNullOrWhiteSpace(sessionKey) || sessionKey.Length != SessionKeyLength) 15 { 16 17 // 生成36位随机数 18 var guidBytes = new byte[16]; 19 CryptoRandom.GetBytes(guidBytes); 20 sessionKey = new Guid(guidBytes).ToString(); 21 22 // 加密cookieValue 23 cookieValue = CookieProtection.Protect(_dataProtector, sessionKey); 24 25 // 设置Cookie 26 var establisher = new SessionEstablisher(context, cookieValue, _options); 27 tryEstablishSession = establisher.TryEstablishSession; 28 isNewSessionKey = true; 29 } 30 31 var feature = new SessionFeature(); 32 // 创建Sessin放入 HttpContext Features 33 feature.Session = _sessionStore.Create(sessionKey, _options.IdleTimeout, _options.IOTimeout, tryEstablishSession, isNewSessionKey); 34 context.Features.Set (feature); 35 36 try 37 { 38 // 执行逻辑(MVC)之间 39 await _next(context); 40 // 执行逻辑(MVC)之后 41 } 42 finally 43 { 44 // 设置HttpContext Features为空 45 context.Features.Set (null); 46 47 if (feature.Session != null) 48 { 49 try 50 { 51 // Commit Session,把 IDictionary 中的值放入缓存 52 await feature.Session.CommitAsync(context.RequestAborted); 53 } 54 catch (OperationCanceledException) 55 { 56 _logger.SessionCommitCanceled(); 57 } 58 catch (Exception ex) 59 { 60 _logger.ErrorClosingTheSession(ex); 61 } 62 } 63 } 64 }
4.3、DistributedSession.cs 类解析
在SessionMiddleware Invoke方法中,可以看到创建Session最终执行的是new DistributedSession();
此类就不做过多的介绍了,主要就是对IDictionary
代码过多就不放置博客上,可移至github :https://github.com/aspnet/Session/blob/master/src/Microsoft.AspNetCore.Session/DistributedSession.cs
5、总结
1、在asp.net core中Session的代码还是比较简单的,运用操作也比较简单;
2、可以清楚的理解asp.net core中Session的原理;
3、可以学习其他生产随机数的方法;
4、可以学习在中间件中怎么运用设置(Options.Create(options)、services.Configure(configure));
5、知道了中间件的简单运用;
6、学写了Httpcontext Features 的简单运用,关于 HttpContext可以直接使用Session(HttpContext.Session)在讲asp.net core http时会详细介绍;
7、简单知道了对于缓存的获取和增加;
8、下一篇将分析 .net core configuration,敬请关注;
9、记得推荐评论,或者可以留言希望分析哪部分asp.net core的源码
SignalR
从上面的介绍可以看出,SignalR既然是为实时而生的,这样就决定了其使用场所。具体适用情景有如下几点:
- 聊天室,如在线客服系统,IM系统等
- 股票价格实时更新
- 消息的推送服务
- 游戏中人物位置的实时推送 : 游戏参考 https://blog.csdn.net/jaswhen/article/details/48517999
Asp.net SignalR是微软为实现实时通信的一个类库。一般情况下,signalR会使用JavaScript的长轮询(long polling)的方式来实现客户端和服务器通信,随着Html5中WebSockets出现,SignalR也支持WebSockets通信。另外SignalR开发的程序不仅仅限制于宿主在IIS中,也可以宿主在任何应用程序,包括控制台,客户端程序和Windows服务等,另外还支持Mono,这意味着它可以实现跨平台部署在Linux环境下。
signalR内部有两类对象:
- Http持久连接(Persisten Connection)对象:用来解决长时间连接的功能。还可以由客户端主动向服务器要求数据,而服务器端不需要实现太多细节,只需要处理PersistentConnection 内所提供的五个事件:OnConnected, OnReconnected, OnReceived, OnError 和 OnDisconnect 即可。
- Hub(集线器)对象:用来解决实时(realtime)信息交换的功能,服务端可以利用URL来注册一个或多个Hub,只要连接到这个Hub,就能与所有的客户端共享发送到服务器上的信息,同时服务端可以调用客户端的脚本。SignalR将整个信息的交换封装起来,客户端和服务器都是使用JSON来沟通的,在服务端声明的所有Hub信息,都会生成JavaScript输出到客户端,.NET则依赖Proxy来生成代理对象,而Proxy的内部则是将JSON转换成对象。
SignalR将整个信息的交换封装起来,客户端和服务器都是使用JSON来沟通的,在服务端声明的所有Hub信息,都会生成JavaScript输出到客户端,.NET则依赖Proxy来生成代理对象,而Proxy的内部则是将JSON转换成对象。
SignalR的服务端提供了两种实现方式,分别是PersistentConnection和Hub,这两种方式的侧重点不同:
PersistentConnection | Hub/生成Proxy模式 | Hub/非生成Proxy模式 | |
服务端配置 | app.Map("/messageConnection", map => |
app.Map("/messageHub", map => |
app.Map("/messageHub", map => |
引入js文件 | jquery-1.6.4.min.js jquery.signalR-2.2.0.min.js |
jquery-1.6.4.min.js jquery.signalR-2.2.0.min.js /messageHub/js 上述js文件是动态生成,其中messageHub的为服务端定义的路径 |
jquery-1.6.4.min.js jquery.signalR-2.2.0.min.js |
创建连接 | var connection = $.connection("/message"); | var connection = $.connection; | var connection = $.hubConnection(); |
开启连接 | connection.start() |
connection.hub.start() |
connection.start() |
代理对象 | 无 | var proxy = connection.MessageService; MessageService是Hub的名称 |
var proxy = connection.createHubProxy("MessageService"); MessageService是Hub的名称 |
定义客户端方法 | 无 | proxy.client.hello = function (message) { } |
proxy.on("hello", function (message) { console.log(message); }); |
接收消息 | connection.received(function (message) { |
通过服务器调用客户端方法实现 |
通过服务器调用客户端方法实现 |
发送消息 | connection.send(message); | 通过调用服务端方法实现 proxy.server.hello(message); |
通过调用服务端方法实现 proxy.invoke("hello", message); |
设置QueryString | 在创建connection时指定 var connection = $.connection("/messageConnection", { username: "qs" + username }); |
connection.hub.qs = { username: "qs" + username }; |
connection.qs = { username: "qs" + username }; |
设置Cookie | document.cookie = "username=" + username; | document.cookie = "username=" + username; | document.cookie = "username=" + username; |
设置State | 无 | proxy.state.ClientType = "HubAutoProxy"; | proxy.state.ClientType = "HubNonAutoProxy"; |
示例代码下载
docs.microsoft t
容易碰到的问题:
1.预定义的类型“Microsoft.CSharp.RuntimeBinder.Binder”未定义或未导入:https://blog.csdn.net/rztyfx/article/details/61432763
2.Owin:
Install-Package microsoft.owin.cors
Update-Package Owin -Reinstall
3.关于SignalR连接数量问题的记录:https://blog.csdn.net/Andrewniu/article/details/80243120
sql for xml path用法
FOR XML PATH 有的人可能知道有的人可能不知道,其实它就是将查询结果集以XML形式展现,有了它我们可以简化我们的查询语句实现一些以前可能需要借助函数活存储过程来完成的工作。那么以一个实例为主.
一.FOR XML PATH 简单介绍
那么还是首先来介绍一下FOR XML PATH ,假设现在有一张兴趣爱好表(hobby)用来存放兴趣爱好,表结构如下:
接下来我们来看应用FOR XML PATH的查询结果语句如下:
结果:
由此可见FOR XML PATH 可以将查询结果根据行输出成XML各式!
那么,如何改变XML行节点的名称呢?代码如下:
结果一定也可想而知了吧?没错原来的行节点
这个时候细心的朋友一定又会问那么列节点如何改变呢?还记的给列起别名的关键字AS吗?对了就是用它!代码如下:
那么这个时候我们列的节点名称也会编程我们自定义的名称
噢! 既然行的节点与列的节点我们都可以自定义,我们是否可以构建我们喜欢的输出方式呢?还是看代码:
没错我们还可以通过符号+号,来对字符串类型字段的输出格式进行定义。结果如下:
那么其他类型的列怎么自定义? 没关系,我们将它们转换成字符串类型就行啦!例如:
好的 FOR XML PATH就基本介绍到这里吧,更多关于FOR XML的知识请查阅帮助文档!
接下来我们来看一个FOR XML PATH的应用场景吧!那么开始吧。。。。。。
二.一个应用场景与FOR XML PATH应用
首先呢!我们在增加一张学生表,列分别为(stuID,sName,hobby),stuID代表学生编号,sName代表学生姓名,hobby列存学生的爱好!那么现在表结构如下:
这时,我们的要求是查询学生表,显示所有学生的爱好的结果集,代码如下:
SELECT sName,
(SELECT hobby+',' FROM student
WHERE sName=A.sName
FOR XML PATH('')) AS StuList
FROM student A
GROUP BY sName
) B
结果如下:
分析: 好的,那么我们来分析一下,首先看这句:
WHERE sName=A.sName
FOR XML PATH('')
这句是通过FOR XML PATH 将某一姓名如张三的爱好,显示成格式为:“ 爱好1,爱好2,爱好3,”的格式!
那么接着看:
SELECT sName,
(SELECT hobby+',' FROM student
WHERE sName=A.sName
FOR XML PATH('')) AS StuList
FROM student A
GROUP BY sName
) B
剩下的代码首先是将表分组,在执行FOR XML PATH 格式化,这时当还没有执行最外层的SELECT时查询出的结构为:
可以看到StuList列里面的数据都会多出一个逗号,这时随外层的语句:SELECT B.sName,LEFT(StuList,LEN(StuList)-1) as hobby 就是来去掉逗号,并赋予有意义的列明!
MemCahe
首先介绍下memcahce的定义:是一个分布式的高速缓存系统,目前被许多网站使用以提升网站的访问速度,尤其对于一些大型的、需要频繁访问数据库的网站访问速度提升效果十分显著。
接下来介绍下在windows下的memcache的安装与使用:
第一步:下载memcache安装包
链接:下载mamcache安装包 密码: kwt5
第二步:安装memcache服务:
1 开启memcache服务:首先查看自己计算机所开启的服务----在开始菜单查询里输入命令:services.msc
接下来在dos窗口安装memcache服务-----在开始菜单查询里输入cmd指令,打开dos黑窗口
接下来输入指令来开启memcache服务
那么这个时候刷新下服务,就会出现memcached server
谈到这个地方,有些人不明白memcache 与memcached的区别,这里简要说下,memecache是项目名,叫分布式缓存系统,而memcached.exe是一个程序名,只是项目中的一个启动服务的程序。
接下来通过telnet命令,来连接服务器的memcache端口,往memcache里面添加数据,进行一些简单操作。
telnet:是TCP/IP协议族中的一员,简单说就是可以通过telnet指令来连接指定的服务器(可以是多台服务器),对服务器中的内容进行操作。
首先开启telnet本机服务----打开控制面板,选择程序和功能,打开或关闭windows功能,在Telnet客户端上打钩。
下一步:通过telnet命令连接本地服务器的memcache端口
连接成功后的结果:
如果连接不成功,请检查下telnet客户端功能有没有添加,memcache服务状态是否是已启动。
接下来开始玩一下memcache。
首先输入stats,返回统计信息例如 PID(进程号)、版本号、连接数等
这里面的参数参考:http://www.runoob.com/memcached/memcached-stats.html
接下来就是一些简单的指令操作,具体的学习参考上面的链接。
学习到这里,我相信基本的memcache使用应该会了。接下来写了一个demo,也比较简单。
首先需要下载memcache,net库 下载地址:链接: 下载memcache,net库 密码: fsfw
引用三个dll文件:ICSharpCode.SharpZipLib.dll log4net.dll Memcached.ClientLibrary.dll
namespace MemCacheDemo { class Program { static void Main(string[] args) { string[] servers = { "127.0.0.1:11211" };// 127.0.0.1:服务器地址,11211:memcache端口号 # region 初始化池 SockIOPool pool = SockIOPool.GetInstance(); //设置服务器列表 pool.SetServers(servers); //各服务器之间负载均衡的设置比例 pool.SetWeights(new int[] { 1 }); //初始化时创建连接数 pool.InitConnections = 3; //最小连接数 pool.MinConnections = 3; //最大连接数 pool.MaxConnections = 5; //连接的最大空闲时间,下面设置为6个小时(单位ms),超过这个设置时间,连接会被释放掉 pool.MaxIdle = 1000 * 60 * 60 * 6; //socket连接的超时时间,下面设置表示不超时(单位ms),即一直保持链接状态 pool.SocketConnectTimeout = 0; //通讯的超时时间,下面设置为3秒(单位ms),.Net版本没有实现 pool.SocketTimeout = 1000 * 3; //维护线程的间隔激活时间,下面设置为30秒(单位s),设置为0时表示不启用维护线程 pool.MaintenanceSleep = 30; //设置SocktIO池的故障标志 pool.Failover = true; //是否对TCP/IP通讯使用nalgle算法,.net版本没有实现 pool.Nagle = false; //socket单次任务的最大时间(单位ms),超过这个时间socket会被强行中端掉,当前任务失败。 pool.MaxBusy = 1000 * 10; pool.Initialize(); #endregion #region 客户端实例 MemcachedClient Cache = new MemcachedClient(); //是否启用压缩数据:如果启用了压缩,数据压缩长于门槛的数据将被储存在压缩的形式 Cache.EnableCompression = false; //压缩设置,超过指定大小的都压缩 //cache.CompressionThreshold = 1024 * 1024; Cache.Add("myKey", "523146");//向memcache缓存里添加数据 #endregion pool.Shutdown();//关闭连接池 } } }
对上面的代码解释下:首先设置连接的服务器,及memcache端口号 接下来初始化sock线程池 实例化memcache客户端,添加键值。
运行后,可以在dos窗口中获取myKey值。
看到这里,相信读者有了一点感觉了。
接下来,谈谈为什么使用memcache能够提高用户的访问速度?
假设有10万个用户请求网站,那么后台程序会到数据库里查询用户数据,将查询到的数据放入到memcache中,这里会为每个用户随机产生一个guid用来表示key,登录信息的数据放在value中,然后根据guid通过hash算法产生特定的哈希值用来将用户信息按照一定的规则存储在memcache中。这样子,当用户访问主页的时候,首先会到memcache缓存里查询是否有登录信息,而不需要到数据库里查询。同时因为每个用户信息都按照一定的规则存放在缓存中,所以到缓存里查询数据时,会节省时间。
写下这篇文章主要记录自己学习的点点滴滴,当然这篇博客里也参考了博友们的文章和一些视频资料。如果读者有什么不明白地方,或者觉得文章有错误,欢迎指正,谢谢。
C# 操作Excel图形——绘制、读取、隐藏、删除图形
简介
本篇文章将介绍C# 如何处理Excel图形相关的问题,包括以下内容要点:
1.绘制图形
1.1 绘制图形并添加文本到图形
1.2 添加图片到图形
1.3 设置图形阴影效果
2. 提取图形中的文本、图片
3. 设置图形的显示、隐藏
4. 删除图形
4.1删除指定图形
4.2 删除所有图形
所需工具
- Free Spire.XLS for .NET 8.3 (社区版)
PS: 下载安装该类库后,注意在项目程序中添加引用Spire.Xls.dll文件(dll文件可在安装路径下的Bin文件夹中获取)
注:Spire.xls能支持的图形种类很多,常见的Office Excel中的图形,这个类库也都能实现,
示例代码(供参考)
1. 绘制图形
【C#】
using System.Drawing; using Spire.Xls; using Spire.Xls.Core; namespace Add_shapes_to_Excel { class Program { static void Main(string[] args) { //创建实例 Workbook workbook = new Workbook(); //获取第一个工作表 Worksheet sheet = workbook.Worksheets[0]; //添加“太阳”形状的图形,并填充颜色 IPrstGeomShape Triangle = sheet.PrstGeomShapes.AddPrstGeomShape(2, 2, 170, 160, PrstGeomShapeType.Sun); Triangle.Fill.ForeColor = Color.Orange; Triangle.Fill.FillType = ShapeFillType.SolidColor; Triangle.Text = "IT'S A SUNNY DAY";//添加文本 //添加“禁止”标志的图形,并填充渐变颜色 IPrstGeomShape heart = sheet.PrstGeomShapes.AddPrstGeomShape(2, 7, 140, 140, PrstGeomShapeType.NoSmoking); heart.Fill.ForeColor = Color.Red; heart.Fill.FillType = ShapeFillType.Gradient; //添加云朵形状的图形 IPrstGeomShape Cloud = sheet.PrstGeomShapes.AddPrstGeomShape(15, 2, 160, 160, PrstGeomShapeType.Cloud); //设置图形阴影效果 Cloud.Shadow.Angle = 90; Cloud.Shadow.Distance = 10; Cloud.Shadow.Size = 100; Cloud.Shadow.Color = Color.SteelBlue; Cloud.Shadow.Blur = 30; Cloud.Shadow.Transparency = 1; Cloud.Shadow.HasCustomStyle = true; //添加五角星形状的图形,并加载图片来填充图形 IPrstGeomShape cloud = sheet.PrstGeomShapes.AddPrstGeomShape(15, 7, 160, 160, PrstGeomShapeType.Star5); cloud.Fill.CustomPicture(Image.FromFile("sm.png"), "sm.png"); cloud.Fill.FillType = ShapeFillType.Picture; //保存并打开文档 workbook.SaveToFile("AddShapes.xlsx", ExcelVersion.Version2013); System.Diagnostics.Process.Start("AddShapes.xlsx"); } } }
图形插入效果:
2.提取图形中的文本和图片
【C#】
using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Text; using Spire.Xls; using Spire.Xls.Core; namespace Extract_text_and_image_from_Excel_shape { class Program { static void Main(string[] args) { //创建实例,加载Excel工作表 Workbook workbook = new Workbook(); workbook.LoadFromFile("test.xlsx"); //获取第一个工作表 Worksheet sheet = workbook.Worksheets[0]; //提取指定形状中的文本内容,并将提取到的文本保存到指定文档 IPrstGeomShape shape1 = sheet.PrstGeomShapes[0]; string s = shape1.Text; StringBuilder sb = new StringBuilder(); sb.AppendLine(s); File.WriteAllText("ExtractText.txt", sb.ToString()); System.Diagnostics.Process.Start("ExtractText.txt"); //提取指定图形中的图片,并保存图片到指定文件 IPrstGeomShape shape2 = sheet.PrstGeomShapes[3]; Image image = shape2.Fill.Picture; image.Save("ShapeImage.png", ImageFormat.Png); System.Diagnostics.Process.Start("ShapeImage.png"); } } }
提取结果:
3. 设置图形的隐藏、显示
【C#】
using Spire.Xls; namespace HideShapes_XLS { class Program { static void Main(string[] args) { //创建实例,加载Excel文档 Workbook workbook = new Workbook(); workbook.LoadFromFile("test.xlsx"); //获取第一个工作表 Worksheet sheet = workbook.Worksheets[0]; //隐藏第3个图形 sheet.PrstGeomShapes[2].Visible = false; //显示图形 //sheet.PrstGeomShapes[1].Visible = true; //保存并打开文档 workbook.SaveToFile("HideShape.xlsx", ExcelVersion.Version2013); System.Diagnostics.Process.Start("HideShape.xlsx"); } } }
设置效果:
4. 删除Excel图形
【C#】
using Spire.Xls; namespace RemoveShapes_XLS { class Program { static void Main(string[] args) { //实例化Workbook类对象,加载Excel文件 Workbook workbook = new Workbook(); workbook.LoadFromFile("test.xlsx"); //获取第一个工作表 Worksheet sheet = workbook.Worksheets[0]; //删除第一个图形 sheet.PrstGeomShapes[0].Remove(); //删除所有图形 //for (int i = sheet.PrstGeomShapes.Count-1; i >= 0; i--) //{ // sheet.PrstGeomShapes[i].Remove(); //} //保存并打开文件 workbook.SaveToFile("DeleteShape.xlsx", ExcelVersion.Version2013); System.Diagnostics.Process.Start("DeleteShape.xlsx"); } } }
图形删除效果:
以上是关于“C#操作Excel中图形”的介绍,如需转载,请注明出处。
ASP.NET MVC 是微软官方提供的以MVC模式为基础的ASP.NET Web应用程序(Web Application)框架,它由Castle的MonoRail而来。
MVC 编程模式
MVC 是三种 ASP.NET 编程模式中的一种。
MVC 是一种使用 MVC(Model View Controller 模型-视图-控制器)设计创建 Web 应用程序的模式。
(1)Model(模型)表示应用程序核心(比如数据库记录列表)。
(2)View(视图)显示数据(数据库记录)。
(3)Controller(控制器)处理输入(写入数据库记录)。
MVC 模式同时提供了对 HTML、CSS 和 JavaScript 的完全控制。Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。View(视图)是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的。Controller(控制器)是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。
新建一个ASP.NET MVC4应用程序,结构如下图所示:
对各个文件夹的说明:
(1)App_Data 文件夹用于存储应用程序数据。
(2)Content 文件夹用于存放静态文件,比如样式表(CSS 文件)、图标和图像。
(3)Controllers 文件夹包含负责处理用户输入和相应的控制器类。
(4)Models 文件夹包含表示应用程序模型的类。模型控制并操作应用程序的数据。
(5)Views 文件夹用于存储与应用程序的显示相关的 HTML 文件(用户界面)。
(6)Scripts 文件夹存储应用程序的 JavaScript 文件。
下面就主要的Controller、Model和View做出说明。
一、控制器
1、描述
控制器(Controller)主要负责响应用户的输入,并在响应时修改模型(Model)。通过这种方式,控制器主要关注的是应用程序流、输入数据的处理,以及对相关视图(View)输出数据的提供。
2、简单控制器
新建一个ASP.NET MVC4应用程序,会自动生成一个HomeController和AccountController。
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace MvcApplication1.Controllers { public class HomeController : Controller { public ActionResult Index() { ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application."; return View(); } } }
直接按F5运行程序即可看到index试图中的内容,此时浏览器的URL为:
http://localhost:4573,或者修改浏览器地址为:
http://localhost:4573/home
http://localhost:4573/home/index
程序默认会托管在VS2013自带的IIS中,采用的端口号为4573。如果程序托管在MyWeb.com中,则URL应为:
http://MyWeb.com/home/index。
3、控制器操作中的参数
前面的例子Action中返回的是字符串常量,下面就让它们通过相应URL传进来的参数动态地执行操作。
在这里,我们使用HttpUtility.HtmlEncode来预处理用户输入。这样就能阻止用户用链接向视图中注入JavaScriptd代码或HTML标记,比如/home/sayhello?content=。
public string SayHello(string content) { string message = HttpUtility.HtmlEncode("Hello " + content); return message; }
-
-
public string Details(int Id) { return "ID:" + Id; }
4、路由---将URL映射到动作
框架是如何知道将URL映射到一个特定的控制动作的?答案就在Global.asax文件的RegisterRoutes方法中。该方法定义了将一个URL模式映射到控制器或动作的路由。
using System.Linq; using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; namespace MvcApplication1 { // 注意: 有关启用 IIS6 或 IIS7 经典模式的说明, // 请访问 http://go.microsoft.com/?LinkId=9394801 public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); AuthConfig.RegisterAuth(); } } }
- 在RegisterRoutes方法上按F12,转到该方法的定义,如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; namespace MvcApplication1 { public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } } }
5、控制器总结
(1)不需要做任何配置,只需浏览到/控制器/动作URL即可;
(2)控制器是一个非常简单的类,继承自System.Web.Mvc.Controller类;
(3)用控制器在浏览器中显示文本,没有用到View或Model。
二、视图
1、作用
提供用户界面。一个控制器可以对应多个试图,一个视图可以被多个控制器使用。
在Action名上右键→添加试图→View1。
2、指定视图
@{ Layout = null; }View1 @ViewBag.Message
3、ViewData和ViewBag的区别于联系
在前面的例子中,使用了ViewBag的Message属性从控制器往视图传递数据,ViewData是一个特殊的字典类,可以按标准语法进行使用:ViewData["CurrentTime"]=DateTime.Now;
这种语法也可以用动态封装器ViewBag:ViewBag.CurrentTime=DateTime.Now;
注意:
(1)尽管只有当要访问的关键字是有效的C#标识符是,ViewBag才起作用,如在ViewData["Key With Spaces"]就不能使用ViewBag访问,编译不通过;
(2)动态值不能作为一个参数传递给扩展方法。因为C#编译器为了选择正确的扩展方法,在编译是必须知道每个参数真正类型。如:@Html.TextBox("name",ViewBag.Name)不会编译通过,可以改为:
①@Html.TextBox("name",ViewData["Name"])
②@Html.TextBox("name",(string)ViewBag.Name)
4、强类型视图
现在需要编写一个显示Album实例列表的视图。一简单的方法就是通过ViewBag属性把那些Album实例添加到视图数据字典中,然后在视图中迭代他们。
(1)首先,在Models文件夹下新建一个Album类,为了简单起见,只定义一个Title属性。
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace MvcApplication1.Models { public class Album { public string Title { get; set; } } }
- (2)控制器
public ActionResult List() { var album = new List(); for (int i = 0; i < 10; i++) { album.Add(new Album { Title = "Product " + i.ToString() }); } //一、使用ViewBag传值 //ViewBag.Album = album; //return View("ListView"); //二、使用ViewData传值 ViewData["Album"] = album; return View("ListView"); }
(3)视图
在List上右键→添加视图。
@{ Layout = null; }ListView @*一、使用ViewBag传值*@ @*@foreach(MvcApplication1.Models.Album a in (ViewBag.Album as IEnumerable
)) { - @a.Title
}*@ @*二、使用ViewBag传值*@ @foreach (MvcApplication1.Models.Album a in (ViewData["Album"] as IEnumerable)) { - @a.Title
}@foreach(dynamic d in ViewBag.Album) {
- @d.Title
}
运行效果:
-
@foreach (dynamic d in ViewBag.Album)
{
- @d.Title }
请记住,ViewData是ViewDataDictionary类型的,它有一个额外的Model属性,可以用来在视图中获取指定的模型对象。由于在ViewData中只能传递一个模型对象,因此,我们利用这一点可以很容易的实现向视图传递一个特定的类对象。
在Controller方法中,可以通过重载的List方法中传递模型实例来指定模型,代码如下:
public ActionResult List() { var album = new List(); for (int i=0;i < 10; i++) { album.Add(new Album { Title="Product " + i.ToString() }); } ViewData["Album"]=album; return View("ListView",album); } @model IEnumerable @{ Layout = null; } ListView @foreach(dynamic d in Model) {
- @d.Title
}
如果不想输入模型类型的完全限定类型名,可使用using关键字声明,如下所示:
@using MvcApplication1.Models @model IEnumerable@{ Layout = null; } ListView @foreach (dynamic d in Model) {
- @d.Title
}
对于在视图中经常使用的名称空间,一个较好的方法就是在Views目录下的web.config文件中声明。
< pages pageBaseType="System.Web.Mvc.WebViewPage">
5、Razor视图引擎
5.1 先来看一个简单的例子。
@{ Layout = null; } @{ var items = new string[] { "one", "two", "three" }; }ListView @foreach (var item in items) {
- @item
}
5.2 Html编码
因为在很多情况下需要用视图显示用户输入,如博客评论等,所以总是存在着潜在的跨站脚本注入攻击(也成XSS),值得称赞的是,Razor表达式是Html自动编码的,如下不会弹出一个警示框,而是直接显示html代码。
@{ string message = ""; }
然而,如果想要展示Html标签,就要返回一个System.Web.IHtml对象的实例,Razor并不对它进行编码。当然也可以创建一个HtmlStringl实例或者使用Html.Raw便捷方法。
@{
string message = "";
}
@Html.Raw(message)
- 效果:
虽然这种自动的HTML编码通过对一HTML形式显示的用户输入进行编码能有效的缓和XSS的脆弱性,但是对于在JavaScript中时远远不够的。例如:
- 当在JavaScript中将用户提供的值赋给变量时,要使用JavaScript字符串编码而不仅仅是HTML编码,记住这一点是很重要的,也就是要用@Ajax.JavaScriptStringEncode方法对用户输入进行编码。
5.3 布局
Razor的布局有助于使用应用程序中的多个视图保持一致的外观,可使用布局为网站定义公共模板(或只是其中的一部分),公共模板包含一个或多个占位符,应用程序中的其他视图为他们提供内容。
下面看一个非常简单的布局,新建一个名称为MyLayout.cshtml的视图,由于要作为公共模板,所以将其放在/Views/Shared/路径下。
@ViewBag.Title @ViewBag.Title
@RenderBody()
- 其中的@RenderBody()称为占位符,用来标记使用这个模板的视图将渲染他们的主要内容的位置。
接下来新建一个视图,将使用其作为模板。
@{ Layout = "~/Views/Shared/MyLayout.cshtml"; ViewBag.Title = "GoodLuck"; }This is the main content
- 运行效果如下:
布局可能有多个节,例如下面示例在前面的布局基础上添加了一个页脚节:
@ViewBag.Title @ViewBag.Title
@RenderBody()
- 在不做任何改变的情况下再次运行前面的视图,将会抛出一个异常,提示没有定义的Footer节。
默认情况下,视图必须为布局中定义的没一个节提供相应的内容。更新后的View1视图如下所示:
@{ Layout = "~/Views/Shared/MyLayout.cshtml"; ViewBag.Title = "GoodLuck"; }This is the main content
@section Footer{ This is the footer. }
RenderSection方法有一个重载的版本,允许在布局中指定不需要的节,可以给required参数传递一个false值来标记Footer节是可选的:
@ViewBag.Title @ViewBag.Title
@RenderBody()
5.4 ViewStart
在前面的例子中,每一个视图都是用Layout属性来指定它的布局,如果多个视图使用同一个布局,就会产生冗余,并且很难维护。
_ViewStart.cshtml页面可用来消除这种冗余,这个文件的代码先于同目录下任何视图代码的执行,这个文件也可以递归地应用到子目录下的任何视图。
@{
Layout="~/Views/Shared/_Layout.cshtml";
}
因为这个代码先于任何视图执行,所以一个视图可以重写Layout属性的默认值,从而重新选择一个不同的布局。
5.4 指定部分视图
除了返回视图之外,操作方法也可以通过PartialView方法以PartialResult的形式返回部分视图。
The end
ASP.NET MVC 是微软官方提供的以MVC模式为基础的ASP.NET Web应用程序(Web Application)框架,它由Castle的MonoRail而来。
MVC 编程模式
MVC 是三种 ASP.NET 编程模式中的一种。
MVC 是一种使用 MVC(Model View Controller 模型-视图-控制器)设计创建 Web 应用程序的模式。
(1)Model(模型)表示应用程序核心(比如数据库记录列表)。
(2)View(视图)显示数据(数据库记录)。
(3)Controller(控制器)处理输入(写入数据库记录)。
MVC 模式同时提供了对 HTML、CSS 和 JavaScript 的完全控制。Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。View(视图)是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的。Controller(控制器)是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。
新建一个ASP.NET MVC4应用程序,结构如下图所示:
对各个文件夹的说明:
(1)App_Data 文件夹用于存储应用程序数据。
(2)Content 文件夹用于存放静态文件,比如样式表(CSS 文件)、图标和图像。
(3)Controllers 文件夹包含负责处理用户输入和相应的控制器类。
(4)Models 文件夹包含表示应用程序模型的类。模型控制并操作应用程序的数据。
(5)Views 文件夹用于存储与应用程序的显示相关的 HTML 文件(用户界面)。
(6)Scripts 文件夹存储应用程序的 JavaScript 文件。
下面就主要的Controller、Model和View做出说明。
一、控制器
1、描述
控制器(Controller)主要负责响应用户的输入,并在响应时修改模型(Model)。通过这种方式,控制器主要关注的是应用程序流、输入数据的处理,以及对相关视图(View)输出数据的提供。
2、简单控制器
新建一个ASP.NET MVC4应用程序,会自动生成一个HomeController和AccountController。
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace MvcApplication1.Controllers { public class HomeController : Controller { public ActionResult Index() { ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application."; return View(); } } }
直接按F5运行程序即可看到index试图中的内容,此时浏览器的URL为:
http://localhost:4573,或者修改浏览器地址为:
http://localhost:4573/home
http://localhost:4573/home/index
程序默认会托管在VS2013自带的IIS中,采用的端口号为4573。如果程序托管在MyWeb.com中,则URL应为:
http://MyWeb.com/home/index。
3、控制器操作中的参数
前面的例子Action中返回的是字符串常量,下面就让它们通过相应URL传进来的参数动态地执行操作。
在这里,我们使用HttpUtility.HtmlEncode来预处理用户输入。这样就能阻止用户用链接向视图中注入JavaScriptd代码或HTML标记,比如/home/sayhello?content=。
public string SayHello(string content) { string message = HttpUtility.HtmlEncode("Hello " + content); return message; }
public string Details(int Id) { return "ID:" + Id; }
4、路由---将URL映射到动作
框架是如何知道将URL映射到一个特定的控制动作的?答案就在Global.asax文件的RegisterRoutes方法中。该方法定义了将一个URL模式映射到控制器或动作的路由。
using System.Linq; using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; namespace MvcApplication1 { // 注意: 有关启用 IIS6 或 IIS7 经典模式的说明, // 请访问 http://go.microsoft.com/?LinkId=9394801 public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); AuthConfig.RegisterAuth(); } } }
在RegisterRoutes方法上按F12,转到该方法的定义,如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; namespace MvcApplication1 { public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } } }
5、控制器总结
(1)不需要做任何配置,只需浏览到/控制器/动作URL即可;
(2)控制器是一个非常简单的类,继承自System.Web.Mvc.Controller类;
(3)用控制器在浏览器中显示文本,没有用到View或Model。
二、视图
1、作用
提供用户界面。一个控制器可以对应多个试图,一个视图可以被多个控制器使用。
在Action名上右键→添加试图→View1。
2、指定视图
@{ Layout = null; }View1 @ViewBag.Message
3、ViewData和ViewBag的区别于联系
在前面的例子中,使用了ViewBag的Message属性从控制器往视图传递数据,ViewData是一个特殊的字典类,可以按标准语法进行使用:ViewData["CurrentTime"]=DateTime.Now;
这种语法也可以用动态封装器ViewBag:ViewBag.CurrentTime=DateTime.Now;
注意:
(1)尽管只有当要访问的关键字是有效的C#标识符是,ViewBag才起作用,如在ViewData["Key With Spaces"]就不能使用ViewBag访问,编译不通过;
(2)动态值不能作为一个参数传递给扩展方法。因为C#编译器为了选择正确的扩展方法,在编译是必须知道每个参数真正类型。如:@Html.TextBox("name",ViewBag.Name)不会编译通过,可以改为:
①@Html.TextBox("name",ViewData["Name"])
②@Html.TextBox("name",(string)ViewBag.Name)
4、强类型视图
现在需要编写一个显示Album实例列表的视图。一简单的方法就是通过ViewBag属性把那些Album实例添加到视图数据字典中,然后在视图中迭代他们。
(1)首先,在Models文件夹下新建一个Album类,为了简单起见,只定义一个Title属性。
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace MvcApplication1.Models { public class Album { public string Title { get; set; } } }
(2)控制器
public ActionResult List() { var album = new List(); for (int i=0;i < 10 ;i++) { album.Add(new Album { Title="Product " + i.ToString() }); } //一、使用ViewBag传值 //ViewBag.Album = album; //return View("ListView"); //二、使用ViewData传值 ViewData["Album"] = album; return View("ListView"); }
(3)视图
在List上右键→添加视图。
ListView @*一、使用ViewBag传值*@ @*@foreach(MvcApplication1.Models.Album a in (ViewBag.Album as IEnumerable
)) { - @a.Title
}*@ @*二、使用ViewBag传值*@ @foreach (MvcApplication1.Models.Album a in (ViewData["Album"] as IEnumerable)) { - @a.Title
}@foreach(dynamic d in ViewBag.Album) {
- @d.Title
}
运行效果:
-
@foreach (dynamic d in ViewBag.Album)
{
- @d.Title }
请记住,ViewData是ViewDataDictionary类型的,它有一个额外的Model属性,可以用来在视图中获取指定的模型对象。由于在ViewData中只能传递一个模型对象,因此,我们利用这一点可以很容易的实现向视图传递一个特定的类对象。
在Controller方法中,可以通过重载的List方法中传递模型实例来指定模型,代码如下:
public ActionResult List() { var album = new List(); for (int i=0;i < 10;i++) { album.Add(new Album { Title="Product " + i.ToString() }); } ViewData["Album"]=album; return View("ListView",album); } @model IEnumerable @{ Layout = null; } ListView @foreach(dynamic d in Model) {
- @d.Title
}
如果不想输入模型类型的完全限定类型名,可使用using关键字声明,如下所示:
@using MvcApplication1.Models @model IEnumerable@{ Layout = null; } ListView @foreach (dynamic d in Model) {
- @d.Title
}
对于在视图中经常使用的名称空间,一个较好的方法就是在Views目录下的web.config文件中声明。
5、Razor视图引擎
5.1 先来看一个简单的例子。
@{ Layout = null; } @{ var items = new string[] { "one", "two", "three" }; }ListView @foreach (var item in items) {
- @item
}
5.2 Html编码
因为在很多情况下需要用视图显示用户输入,如博客评论等,所以总是存在着潜在的跨站脚本注入攻击(也成XSS),值得称赞的是,Razor表达式是Html自动编码的,如下不会弹出一个警示框,而是直接显示html代码。
@{
string message = "";
}
@message
然而,如果想要展示Html标签,就要返回一个System.Web.IHtml对象的实例,Razor并不对它进行编码。当然也可以创建一个HtmlStringl实例或者使用Html.Raw便捷方法。
@{
string message = "";
}
@Html.Raw(message)
- 效果:
虽然这种自动的HTML编码通过对一HTML形式显示的用户输入进行编码能有效的缓和XSS的脆弱性,但是对于在JavaScript中时远远不够的。例如:
当在JavaScript中将用户提供的值赋给变量时,要使用JavaScript字符串编码而不仅仅是HTML编码,记住这一点是很重要的,也就是要用@Ajax.JavaScriptStringEncode方法对用户输入进行编码。
5.3 布局
Razor的布局有助于使用应用程序中的多个视图保持一致的外观,可使用布局为网站定义公共模板(或只是其中的一部分),公共模板包含一个或多个占位符,应用程序中的其他视图为他们提供内容。
下面看一个非常简单的布局,新建一个名称为MyLayout.cshtml的视图,由于要作为公共模板,所以将其放在/Views/Shared/路径下。
@ViewBag.Title @ViewBag.Title
@RenderBody()
- 其中的@RenderBody()称为占位符,用来标记使用这个模板的视图将渲染他们的主要内容的位置。
接下来新建一个视图,将使用其作为模板。
@{ Layout = "~/Views/Shared/MyLayout.cshtml"; ViewBag.Title = "GoodLuck"; }This is the main content
布局可能有多个节,例如下面示例在前面的布局基础上添加了一个页脚节:
@ViewBag.Title @ViewBag.Title
@RenderBody()
- 在不做任何改变的情况下再次运行前面的视图,将会抛出一个异常,提示没有定义的Footer节。
默认情况下,视图必须为布局中定义的没一个节提供相应的内容。更新后的View1视图如下所示:
@{ Layout = "~/Views/Shared/MyLayout.cshtml"; ViewBag.Title = "GoodLuck"; }This is the main content
@section Footer{ This is the footer }
RenderSection方法有一个重载的版本,允许在布局中指定不需要的节,可以给required参数传递一个false值来标记Footer节是可选的:
@ViewBag.Title @ViewBag.Title
@RenderBody()
5.4 ViewStart
在前面的例子中,每一个视图都是用Layout属性来指定它的布局,如果多个视图使用同一个布局,就会产生冗余,并且很难维护。
_ViewStart.cshtml页面可用来消除这种冗余,这个文件的代码先于同目录下任何视图代码的执行,这个文件也可以递归地应用到子目录下的任何视图。
@{
Layout="~/Views/Shared/_Layout.cshtml";
}
因为这个代码先于任何视图执行,所以一个视图可以重写Layout属性的默认值,从而重新选择一个不同的布局。
5.4 指定部分视图
除了返回视图之外,操作方法也可以通过PartialView方法以PartialResult的形式返回部分视图。
The end
IOC,DIP,DI,IoC容器
Posted on 2018-07-30 17:34 一个老年人 阅读(58) 评论(0) 编辑 收藏定义
IOC(Inversion of Control 控制反转),DIP(Dependency Inverson Principle 依懒倒置)都属于设计程序时指导原则,并没有具体的实现。比较常用的五大原则SOLID(SRP单一职责、OCP开闭原则、LSP里氏转换原则、IOC、DIP)
DI(Dependency Injection 依懒注入)属于模式,提供了一种具体的处理程序中对应情况的实现
IoC容器 属于一种框架 例如
- Unity
- StructureMap
- Castle Windsor
- Ninject
- Autofac
- DryIoc
- Simple Injector
- Light Inject
我只会autofac,属于菜鸟,欢迎交流
IoC原则:反转控制(一般与DIP一起使用)
用代码演示一下
public class A { public void Task() { var b=new B(); b.DoSomeThing(); } } public class B { public void DoSomeThing() { } }
上面的代码,
类A创建和管理类B的对象的生命周期。它控制依赖类对象的创建和生命周期。
IoC原则建议反转控制,意味着将控制内容分离到另一个类。换句话说,将依赖关系创建控件从A类反转到另一个类,如下所示。
public class A { public void Task() { var b=Factory.GetB(); b.DoSomeThing(); } } public class B { public void DoSomeThing() { } }
A类不直接创建B类,而是通过一个工厂创建。我们就实现了控制反转
DIP原则:高级模块不应该依懒低级模块,两者都应该依懒抽象;抽象不应该依懒具体,具体应该依懒抽象