Winform异步更新UI界面的方法三:真正线程安全的this.Invoke 匿名委托

this.Invoke可以方便的在子线程中对UI界面进行访问,并且是真正的线程安全的.

单纯这一个委托无法实现回调,也就是绑定完成之后无法提供一个后续的操作

使用场景为需要远程读取或处理数据(耗费时间),然后直接绑定到控件显示,无其他后续操作要求.

本方法使用的时候要注意:

                this.Invoke((Action)delegate
                {
                    //这里只应该放置UI控件访问的代码,其他的数据读取和处理要放到外面,因为实际上这个代码块是在主线程中执行的.                    
                    this.label2.Text = i.ToString();
                });
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace chengchenxu.ActionEMO
{
    public partial class Form1 : Form
    {

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //启动累加线程 不停改变Label2内容
            Thread thread2 = new Thread(Add);
            thread2.IsBackground = true;
            thread2.Start();
        }
        private void Add()
        {
            for (int i = 0; i < 50; i++)
            {
                Thread.Sleep(1000);

                //采用匿名委托方式进行访问UI线程
                this.Invoke((Action)delegate
                {
                    //这里的语句实际上是在主线程执行的
                    //子线程委托主线程进行更新
                    //如果下面这样把for语句挪到this.Invoke内 主界面会卡死
                    //this.Invoke((Action)delegate
                    //{
                    //    for (int i = 0; i < 50; i++)
                    //    {
                    //        Thread.Sleep(1000);
                    //        this.label2.Text = i.ToString();
                    //    }

                    //});
                    this.label2.Text = i.ToString();
                });
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //另起线程3
            Thread thread3 = new Thread(Get);
            thread3.Start();
        }

        private void Get()
        {
            
            try
            {
                //尝试从线程3访问主界面 拒绝访问
                this.label2.Text = "efsadddddddddddddddd";



                //如果这里用上this.Invoke委托,则可以访问.
                //this.Invoke((Action)delegate
                //{
                //    this.label2.Text = "efsadddddddddddddddd";
                //});
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            //从主线程中访问label2是可以访问的,因为他本来就在主线程内
            this.label2.Text = "efsadddddddddddddddd";
            this.label2.ForeColor = Color.Red;
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            
        }
    }
}


DEMO下载:chengchenxu.ActionEMO - 副本.rar


Winform异步更新UI界面的方法二:CheckForIllegalCrossThreadCalls=false 放弃线程安全

在窗体中设置以下属性,可以使窗体放弃线程安全检查,那么结果就是任意线程任意时间均可自由互相访问,例如所有线程都可以直接访问UI控件.

这个方法任何时候均不推荐使用.使用不安全的线程容易让程序发生奇怪并难以调试的BUG.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace chengchenxu.com.CheckForIllegalCrossThreadCalls
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            CheckForIllegalCrossThreadCalls = false; //取消线程安全检查 使UI访问不受限制
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //启用线程 进行累加
            Thread thread = new Thread(Add);
            //设为后台线层 否则关闭窗体线程不会停止 直到执行完毕 这里为了便于调试 
            thread.IsBackground = true;
            thread.Start();           
        }
        private void Add()
        {
            for (int i = 0; i < 1000; i++)
            {
                //可以随意访问UI控件
                this.label2.Text = i.ToString();
                Thread.Sleep(50);
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //开启新线程,访问Label2
            Thread thread2 = new Thread(GetLabelText);
            thread2.Start();
        }
        private void GetLabelText()
        {
            //取得Label2的值
            MessageBox.Show("取得值"+this.label2.Text);
        }

        private void button2_Click(object sender, EventArgs e)
        {
            //主线程直接访问Label2
            MessageBox.Show("取得值" + this.label2.Text);
        }

        
    }
}


DEMO:chengchenxu.com.CheckForIllegalCrossThreadCalls.rar


Winform异步更新UI界面的方法一:伪多线程 Timer控件

1 Timer是最简单的实现"类似多线程"的方法

2 Timer只适合执行简单快速的操作

3 Timer是伪多线程,需要定期从主线程(UI线程)中索要执行时间

4 Timer执行时间过长会发生UI卡顿

5 Timer如果每次的执行时间都超过时钟周期,那么会发生周期混乱.


演示代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace ChengChenXu.com.ThreadDemo
{
    public partial class TimerControl : Form
    {
        public TimerControl()
        {
            InitializeComponent();
        }

        private void TimerControl_Load(object sender, EventArgs e)
        {
            //启动Timer1定时器 持续更新时间
            timer1.Start();
        }
        private void timer1_Tick(object sender, EventArgs e)
        {
            //Timer1 周期100毫秒 显示时间 
            //比如本操作需要1毫秒来执行 那么剩余99毫秒将会空闲留给UI 所以UI不会卡死
            this.label2.Text = DateTime.Now.ToString("HH-mm-ss-FF");
        }

       

        private void button1_Click(object sender, EventArgs e)
        {
            //启动Timer2定时器 周期100毫秒
            timer2.Start();
        }

        private void timer2_Tick(object sender, EventArgs e)
        {
            //本操作需要大概需要500毫秒来执行,但是周期只有100毫秒
            //那么将发生线程阻塞,实际表现就是UI会间歇性的卡顿
            //并且周期混乱
            Thread.Sleep(500); //停顿500毫秒

            label4.Text = DateTime.Now.ToString("HH-mm-ss-FF");
        }
    }
}


C#串口通讯教程 简化一切 只保留核心功能 这可能是最易于理解的一篇教程

串口的定义,请自行了解.

C#操作串口通讯在.Net强大类库的支持下,只需要三个步骤:

1 创建

2 打开 

3 发送/接受


1 创建:


1 串口通讯需用用到的命名空间如下:

using System.IO.Ports;

2 因为全局使用,所以声明为全局变量

private SerialPort spSend = new SerialPort();

3 指定串口名称

spSend.PortName = "COM1";
//继续根据需要指定端口的波特率,校验位等信息
//在例子中我们只指定名称,其他的一概不管.


2 打开:

spSend.Open();


3 发送/接收

byte[] data = Encoding.ASCII.GetBytes("要发送的信息");
spSend.Write(data, 0, data.Length);
byte[] data = new byte[spSend.BytesToRead];
spSend.Read(data, 0, data.Length);
String str = new ASCIIEncoding().GetString(data);//收取到的信息


好了,核心代码就是这么简单,下面看完整实例,

界面:

Screen Shot 2018-03-15 at 11.00.20 AM.png

控件名称:下拉框ComList  打开按钮btnOpen 发送框 txtSend 发送按钮btnSend 接收框txtInfo 另外还有一个定时器Timer1


完整源码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO.Ports;//需要的命名空间
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace ChengChenXu.com.COMDemo
{
    public partial class Form1 : Form
    {
        private SerialPort spSend = new SerialPort(); //全局变量
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //获取本机串口列表
            string[] comList = SerialPort.GetPortNames();
            if (comList.Length == 0)  MessageBox.Show("本机没有任何串口");
            //绑定串口列表到下拉列表,设置第一项为默认值   
            foreach (var com in comList)
            {
                ComList.Items.Add(com);
            }
            ComList.SelectedIndex =  0;

            //启动定时器,用来接受信息,没有使用多线程,更易于理解
            timer1.Start();
        }
        private void btnOpen_Click(object sender, EventArgs e)
        {
            if (ComList.Items.Count == 0)
            {
                MessageBox.Show("没有发现串口");
                return;
            }

            //判断是打开操作还是关闭操作
            if (btnOpen.Text == "打开串口")
            {
                if (!spSend.IsOpen)
                {
                    //设置端口名称
                    //这里我们仅仅设置端口的名称,其他的全部用默认.
                    spSend.PortName = ComList.SelectedItem.ToString();

                    try
                    {
                        //打开串口
                        spSend.Open();
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message);
                    }
                    //更新控件状态
                    this.btnOpen.Text = "关闭串口";
                    this.ComList.Enabled = false;
                }
            }
            else if(btnOpen.Text=="关闭串口")
            {
                //关闭串口
                spSend.Close();

                btnOpen.Text = "打开串口";
                ComList.Enabled = true;
            }
        }

        private void btnSend_Click(object sender, EventArgs e)
        {
            //发送数据
            //准备数据 这里我们只实现发送ASCII码 其他的可以先转化为byte[]再发送
            byte[] data = Encoding.ASCII.GetBytes(txtSend.Text);

            if (spSend.IsOpen)
            {
                try
                {
                    //发送动作 参数三个分别为数据 起始偏移位置 长度
                    spSend.Write(data, 0, data.Length);
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                }
            }
            else
            {
                MessageBox.Show("端口还未打开");
            }

        }

        private void Receive()
        {
            //接收信息 先判断是否为打开状态
            if (spSend.IsOpen)
            {
                //准备接收
                byte[] data = new byte[spSend.BytesToRead];
                //接受动作
                spSend.Read(data, 0, data.Length);
                //把接收到的信息转成字符串显示到控件里
                this.txtInfo.Text += new ASCIIEncoding().GetString(data);
            }
        }
        private void timer1_Tick(object sender, EventArgs e)
        {
            //用定时器来定期执行接收动作 间隔100毫秒
            Receive();
        }
    }
}


如何测试

串口通讯既然是通讯那么肯定是需要两方参与的,如何在单机进行测试呢?下面给出几个方法:

1 方法一 把电脑串口的2 3针链接起来,那么接收方和发送方可以为同一个端口.因为2针负责发送,3针负责接收,连接起来即可形成回路

2 使用两台电脑,用串口线相连

3 使用虚拟串口软件,最简单易用,这里我们采用这个方法进行测试.

首先软件下载:VirtualSerialPortDriver 下载地址: VirtualSerialPortDriver.rar

这个是一个收费软件,半个月的试用期,需要的话可以搜索下是否有破解版


安装好之后打开软件,右侧选择好两个准备互联的串口然后点击Add pair即可. 我选择的是COM9和COM10 可以看到左边Virtual ports下面已经有了COM9和COM10了 他们已经可以实现通讯了

Screen Shot 2018-03-15 at 11.16.35 AM.png


把DEMO编译好之后,直接运行两个实例: 一个选择COM9 一个选择COM10 然后都打开串口

Screen Shot 2018-03-15 at 11.18.31 AM.png


现在已经可以互相发送信息了 

由COM9发出的Send for COM9已经发送到COM10

COM9也已经接收到了COM10发出的信息Send for COM10

Screen Shot 2018-03-15 at 11.20.56 AM.png


本例只用了最简单的例子来演示串口通讯过程,简化一切功能,只为更好理解.

源码以及DEMO: ChengChenXu.com.COMDemo.rar


Newtonsoft.Json 简单实用方法 C# Json序列化和反序列化解析操作工具类

Newtonsoft.Json是一个功能多,效率高的Json工具

官方网站:https://www.newtonsoft.com/json

GitHun:https://github.com/JamesNK/Newtonsoft.Json


安装一:

工具-库程序包管理器-库程序包管理器控制台,输入:

Install-Package Newtonsoft.Json

安装二:

项目-右键引用-管理NuGet程序包 可以联机搜索或者搜索已经安装的程序集引入即可


安装三:直接引入DLL文件, 下载地址:

Newtonsoft.Json.rar

版本6.0.0.0


对象的序列化和反序列化:


首先定义一个实体类,只有简单的几个属性

    /// <summary>
    /// 新闻类
    /// </summary>
    public class NewArticle
    {
        //ID
        public int Id { get; set; }
        //标题
        public string Title { get; set; }
        //内容
        public string Content { get; set; }
        //新闻所属分类
        public ArticleClass ArticleClass { get; set; }


    }

    public class ArticleClass
    {
        //分类ID
        public int Cid { get; set; }
        //分类名称
        public string CName { get; set; }
    }


序列化和反序列化演示:

        static void Main(string[] args)
        {
            //实例化一个NewArticle对象
            NewArticle na = new NewArticle
            {
                Id = 1,
                Title = "Newtonsoft.Json的用法简介",
                Content = "Newtonsoft.Json的用法简介的详细文章内容",
                ArticleClass = new ArticleClass { 
                    Cid=1,
                    CName="技术分享"
                }
            };

            //序列化和反序列化:这里主要利用了Newtonsoft.Json.JsonConvert类的SerializeObject(object o)和DeserializeObject<T>(string jsonString)方法

            //序列化为Json字符串
            string jsonString = Newtonsoft.Json.JsonConvert.SerializeObject(na);
            Console.WriteLine("序列化对象");
            Console.WriteLine(jsonString);
            //输出:{\"Id\":1,\"Title\":\"Newtonsoft.Json的用法简介\",\"Content\":\"Newtonsoft.Json的用法简介的详细文章内容\",\"ArticleClass\":{\"Cid\":1,\"CName\":\"技术分享\"}}

            Console.WriteLine("反序列化对象");

            //反序列化:这里利用了泛型方法
            NewArticle na2 = Newtonsoft.Json.JsonConvert.DeserializeObject<NewArticle>(jsonString);

            Console.WriteLine("ID:{0}\nTitle:{1}\nContent:{2}",na2.Id,na.Title,na2.Content);

            Console.ReadKey();

        }

匿名对象的序列化和反序列化

有时候我们仅想使用Json传递一组临时数据给Js进行处理,后台不需要对数据再反序列化,可以利用匿名对象.

删除掉上面代码的Article和ArticleClass类 现在Main方法改为:

        static void Main(string[] args)
        {
           
            var  na = new
            {
                Id = 1,
                Title = "Newtonsoft.Json的用法简介",
                Content = "Newtonsoft.Json的用法简介的详细文章内容",
                ArticleClass = new {
                    Cid = 1,
                    CName = "技术分享"
                }
            };


            //序列化和反序列化:这里主要利用了Newtonsoft.Json.JsonConvert类的SerializeObject(object o)和DeserializeObject<T>(string jsonString)方法

            //序列化为Json字符串
            string jsonString = Newtonsoft.Json.JsonConvert.SerializeObject(na);
            Console.WriteLine("序列化对象");
            Console.WriteLine(jsonString);
            //输出:{\"Id\":1,\"Title\":\"Newtonsoft.Json的用法简介\",\"Content\":\"Newtonsoft.Json的用法简介的详细文章内容\",\"ArticleClass\":{\"Cid\":1,\"CName\":\"技术分享\"}}

            Console.WriteLine("反序列化对象");

            //反序列化:这里利用了非泛型方法 因为匿名类没有固定类型
            var na2 = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonString);

            //匿名类取值需要利用反射,所以需要反序列化的对象一般都不要用匿名对象
            Console.ReadKey();

        }

当运行到最后的时候,可以查看Na2的值:

Screen Shot 2018-03-12 at 12.28.41 PM.png

说明已经反序列化成功了.对于匿名对象想取得属性的话需要用到反射,所以需要反序列化的类一般要实际定义,不要使用匿名对象.

asp.netMVC实例自定义路由规则的设置routes.MapRoute MVC4

首先,本站的路由列表,可以对应本站的URL来验证一下.

         //文章展示页
            routes.MapRoute(
               name: "Show",
               url: "article/{id}/{*url}",
               defaults: new { controller = "Article", action = "Show", url = UrlParameter.Optional }
           );

            //搜索页路由
            routes.MapRoute(
               name: "Search",
               url: "Search-{key}/{*page}",
               defaults: new { controller = "Article", action = "Search", page = UrlParameter.Optional }
           );

            //TAG页路由
            routes.MapRoute(
               name: "Tag",
               url: "Tag-{tag}/{*page}",
               defaults: new { controller = "Article", action = "Tag", page = UrlParameter.Optional }
           );

            //分类页列表
            routes.MapRoute(
               name: "ClassHome",
               url: "ca-{id}-{url}/{*page}",
               defaults: new { controller = "Article", action = "Class", page = UrlParameter.Optional }
           );

            //首页路由,用于分页显示         
            routes.MapRoute(
               name: "HomePage",
               url: "{*page}",
               defaults: new { controller = "Home", action = "Index", page = UrlParameter.Optional },
               constraints: new { page = @"\d+" }
           );
            

            //默认路由
            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{*id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );


 

SQL数据类型和C#数据类型间的转换对照表

<?xml version="1.0" encoding="utf-8" ?>
<Languages>
  <Language From="SQL" To="C# System Types">
    <Type From="bigint" To="System.Int64" />
    <Type From="binary" To="System.Object" />
    <Type From="bit" To="System.Boolean" />
    <Type From="char" To="System.String" />
    <Type From="datetime" To="System.DateTime" />
    <Type From="decimal" To="System.Decimal" />
    <Type From="float" To="System.Double" />
    <Type From="image" To="System.Byte[]" />
    <Type From="int" To="System.Int32" />
    <Type From="money" To="System.Decimal" />
    <Type From="nchar" To="System.String" />
    <Type From="ntext" To="System.String" />
    <Type From="numeric" To="System.Decimal" />
    <Type From="nvarchar" To="System.String" />
    <Type From="real" To="System.Single" />
    <Type From="smalldatetime" To="System.DateTime" />
    <Type From="smallint" To="System.Int16" />
    <Type From="smallmoney" To="System.Decimal" />
    <Type From="text" To="System.String" />
    <Type From="timestamp" To="System.Byte[]" />
    <Type From="tinyint" To="System.Byte" />
    <Type From="uniqueidentifier" To="System.Guid" />
    <Type From="varbinary" To="System.Byte[]" />
    <Type From="varchar" To="System.String" />
    <Type From="xml" To="System.String" />
    <Type From="sql_variant" To="System.Object" />
  </Language>
</Languages>


三行代码实现.NET MVC统计显示页面的执行时间 超简单的实现方法 分析页面执行效率

        博客页脚处添加了页面执行时间统计显示,如下图所示,也可以直接查看网页页脚处.

Screen Shot 2018-03-08 at 12.33.36 PM.png

        实现方法非常简单,只需三行代码(只算主要代码)即可:

        第1行:在Controllers中添加一个BaseController类继承自System.Web.Mvc.Controller,

              然后把其他的Controller类都继承自这个BaseController类,这里不算行数

    
    public abstract class BaseController:System.Web.Mvc.Controller

    public class IndexController : BaseController

    

        第2行:在BaseController类中添加一个无参数构造函数,内容如下:(这里算1.5行)

        
        public BaseController()
        {
            if (System.Web.HttpContext.Current.Session["StarTime"] == null)
            {
                System.Web.HttpContext.Current.Session["StarTime"] = DateTime.Now;
            }
        }

        第3行:在View页的尾部需要展示的地方添加一行代码:(这里算1.5行)

               
               @{
                    TimeSpan ts = DateTime.Now - (DateTime)System.Web.HttpContext.Current.Session["starTime"];
                    int time = Convert.ToInt32(ts.TotalMilliseconds);
                    System.Web.HttpContext.Current.Session.Clear();
                }
                执行时间:@time 毫秒

    好了,现在基本功能就实现了,已经可以显示出来页面的执行时间了.

    

    完毕!这个时间从一开始Controller收到请求开始,到页面绑定结束,主要计算数据库读取以及页面数据绑定的时间.有两部分没有计算到:

    1 服务器从收到HTTP请求开始到框架内部路由到对应的Action的时间,这个时间及其短暂,可以忽略不计.

    2 从服务器执行完毕生成网页HTML代码到客户端加载完的时间,这个是有服务器带宽和用户网速决定的,可以使用JS来获取这个时间.

从零开始实现asp.net MVC4框架网站的用户登录以及权限验证模块 详细教程

    用户登录与权限验证是网站不可缺少的一部分功能,asp.net MVC4框架内置了用于实现该功能的类库,只需要简单搭建即可完成该功能.

下面详细介绍该功能的完成方法,尾部有实例源码下载,希望可以给刚开始接触MVC的朋友做个参考.

    第一步:给VS安装MVC4框架

        VS2012自带MVC4框架,其他版本可以使用独立安装包进行安装,这里就不讨论了,本例使用VS2013创建,.NET4.0+MVC4

    第二步:创建MVC4网站项目

        选择文件-新建-项目,按下图示例创建一个空的MVC网站

        



    第三步:配置web.config,启用Form验证.

        打开根目录下的web.config,在<system.web>节点下插入一下代码,启用Form验证并指定默认登录页面

<authentication mode="Forms">
      <forms loginUrl="/Home/Login" timeout="2880"/>
</authentication>


    第四步:创建所需要文件 

        本例需要创建以下文件:

            Model文件夹下LoginModel.cs   

            Controllers文件夹下HomeController.cs

            View下创建文件下Home,文件夹下创建文件:

                Index.cshtml

                Login.cshtml

                Show.cshtml

                Edit.cshtml

                Add.cshtml

    文件结构如下图所示:

Screen Shot 2018-03-05 at 10.54.43 AM.png


    第五步:填充LoginModel代码  

    public class LoginModel
    {
        [Display(Name="用户名")]
        [Required(ErrorMessage="用户名不能为空")]
        public string UserName { get; set; }

        [Display(Name="密码")]
        [Required(ErrorMessage="密码不能为空")]
        [DataType(DataType.Password)]
        [RegularExpression(@"^\w+$", ErrorMessage = "密码格式有误,只能是字母数字或者下划线")]  
        public string Password { get; set; }


        [Display(Name="记住登陆?")]
        public bool RememberMe { get; set; }

        public string Login()
        {
            //该方法应从数据库中对比用户名和密码,并取得用户权限列表
            //这里为了简单直接对比字符串并返回权限列表,返回NULL则说明用户名或者密码错误
            //权限列表即为用,分割的权限名称
            string result = null;

            if (this.UserName == "guest" & this.Password == "guest")
                result = "Add";

            if (this.UserName == "admin" & this.Password == "admin")
                result = "Add,Edit";

            return result;
        }
    }

        复制上面代码到类中时VS的智能感知会提示你缺少以下命名空间,添加上即可

using System.ComponentModel.DataAnnotations;


    第五步:填充HomeControll代码

public class HomeController : Controller
    {
        //
        // GET: /Home/

        public ActionResult Index()
        {
            ViewBag.Info = "该页面不含权限注解,所有人均可访问.";
            return View();
        }
        
       [Authorize(Roles = "Edit")] //该注解表示只有包含Edit权限的用户才可以访问
        public ActionResult Edit()
        {
            ViewBag.Info = "该页面需要包含Edit权限的人才可访问.";
            return View();
        }

       [Authorize(Roles = "Add")] //该注解表示只有包含Add权限的用户才可以访问
        public ActionResult Add()
        {
            ViewBag.Info = "该页面需要包含Add权限的人才可访问.";
            return View();
        }

       [Authorize(Roles = "Add,Edit")] //该注解表示只有包含Add权限的用户才可以访问
        public ActionResult Show()
        {
            ViewBag.Info = "该页面需要包含Edit或者Add权限的人才可访问.";
            return View();
        }

       public ActionResult Login(LoginModel model)
       {
           return View();
       }

       [HttpPost] //该注解表示只接收Post数据
       [ValidateAntiForgeryToken]//该注解可以防止跨站攻击
       [ActionName("Login")]//该注解可以更改路由中Action名称
       public ActionResult LoginCheck(LoginModel model)
       {
           if (!ModelState.IsValid)
           {
               //用户输入服务端验证,此处处理验证不通过的提示代码 本例略过             
               return View();
           }

           string result = model.Login();

           if (result == null)
           {
               //用户名或者密码不正确的提示代码 本例略过
               return View();
           }
           else
           {
               //用户登陆核心代码
               FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
                    1,
                    model.UserName,
                    DateTime.Now,
                    DateTime.Now.AddHours(240),//记住密码的时间
                    model.RememberMe,//是否保存cookie 记住密码
                    result //获取的用户权限列表 用逗号分割的字符串
                    );
               string encryptedTickt = FormsAuthentication.Encrypt(authTicket);
               HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTickt);
               Response.Cookies.Add(authCookie);

               Response.Redirect("/Home", true);
               ActionResult empty = new EmptyResult();
               return empty;
           }
           return View();
       }


复制以上代码,会提示添加如果命名空间:

using ChengChenXu.MVC4_Login_Demo.Models; //项目Model命名空间
using System.Web.Security;


    第六步:填充View代码,下面代码仅展示<Body>标签内部的内容,页头没有展示(源码中完全).

        Index.cshtml

        <h1>@ViewBag.Info</h1><br />
        <a href="/home/login">登陆</a><br />
        <p>以下链接 登陆后才可以访问 未登陆时如果点击则会跳转到登陆页</p>
        <a href="/home/add">添加页面</a><br />
        <a href="/home/edit">编辑页面</a><br />
        <a href="/home/show">查看页面</a><br />

        Add.cshtml  Edit.cshtml  Show.cshtml 三个文件代码一致

        <h1>@ViewBag.Info</h1>
        <a href="/home">返回</a><br />

        Login.cshtml 该文件首先要在顶部添加一行代码,表示是一个强类型View

@model ChengChenXu.MVC4_Login_Demo.Models.LoginModel

        页面代码:

            @using (Html.BeginForm())
            {
                @Html.AntiForgeryToken()
            
                @Html.LabelFor(model=>model.UserName)
                @Html.TextBoxFor(model => model.UserName)
                <br />

                @Html.LabelFor(model=>model.Password)
                @Html.PasswordFor(model => model.Password)
                <br />
            
                @Html.LabelFor(Model=>Model.RememberMe)
                @Html.CheckBoxFor(model=>model.RememberMe)
                <br />
            
                <input type="submit" class="submit" tabindex="3" value="登录" />

            }

    第七步:修改根目录下Global.asax的代码,添加权限处理代码,把下面两个方法复制添加到Global文件即可

        public MvcApplication()
        {
            AuthorizeRequest += new EventHandler(MvcApplication_AuthorizeRequest);
        }

        void MvcApplication_AuthorizeRequest(object sender, EventArgs e)
        {
            var id = Context.User.Identity as FormsIdentity;
            if (id != null && id.IsAuthenticated)
            {
                var roles = id.Ticket.UserData.Split(',');
                Context.User = new GenericPrincipal(id, roles);
            }
        }

    复制好后会提示缺少以下命名空间:

using System.Web.Security;
using System.Security.Principal;


完成好之后全部实例就完成了,运行以下试试吧.

主页为/Home  无需权限

登录页为/Home/Login  无需权限

添加页为/Home/Add   需要Add权限

展示页为/Home/Show  需要Edit或者Add权限

编辑页为/Home/Edit  需要Edit权限

内置了两个账号 

账号 密码 权限

guest guest "add"  

admin admin "add,edit"


运行结果:

如果直接访问除了Home和Login页面之外的页面(需要权限的页面) 则会跳转到Login页面

guest账号可以访问Add Show页面

admin账号可以访问Add Edit Show页面


锦上添花:

1 自动跳转到登录页的时候URL会有一个ReturnUrl参数记录跳转前的页面,可以捕捉该页面登录后跳回

2 MVC支持客户端验证,需要js文件支持,这样客户端就不再需要写js代码进行输入验证了.本例不再展示了,请自行百度.


源码下载:

ChengChenXu.MVC4_Login_Demo.rar



首页 1 尾页