[转载](一)Socket服务器整体架构概述

mikel阅读(1099)

[转载]【志良教你学Socket】(一)Socket服务器整体架构概述 – 志良的技术博客 – 博客园.

Socket服务器主要用于提供高效、稳定的数据处理、消息转发等服务,它直接决定了前台应用程序的性能。我们先从整体上认识一下Socket服务器,Socket服务器从架构上一般分为:网络层、业务逻辑层、会话层、数据访问层,如图:

(图1)

(一) 网络层

网络层主要用于侦听socket连接、创建socket、接受消息、发送消息、关闭连接。作为socket通信服务器,网络层的性能相当重要,所以 我们在设计网络层时,要着重在以下几方面获得突破:最大连接数、最大并发数、秒处理消息数。如何突破呢?下面我为大家介绍几种网络层常用到的一些技术和技 巧(具体实现,我将在博文中逐一具体阐述):

1)Buffer管理

每一个SocketAsyncEventArgs对象(以下简称SAEA)在内存中都有其对应的缓存空间,如果不对这些缓存空间进行同一管理,当SAEA 对象逐渐增多时,这些SAEA对象的缓存空间会越来越大,它们在系统内存中不是连续的,造成很多内存碎片,而且这些缓存不能重复利用,当创建、销毁 SAEA对象时,造成CPU很多额外消耗,影响服务器性能。面对这问题如何解决呢?用Buffer池管理!

2)双工通信

Socket服务器提高通信效率是一个永恒的话题,提高通信效率有很多种方法,双工通信就是其中之一。一个SAEA对象在同一时刻只能用来接收数据或发送 数据,有人想,如果一个SAEA对象在同一时刻既能发送数据又能接受数据,那肯定会提高socket通信效率。恩,很有想法!可是你能让你的头在同一时刻 既往左转又往右转吗?答案是不行的,那如何实现双工通信呢?既然一个SAEA对象在同一时刻只能做一件事,那我自定义DuplexSAEA对象,在该对象 中封装两个SAEA,一个用于接受,一个用于发送,问题不就解决了吗。

3)poolOfAcceptEventArgs

poolOfAcceptEventArgs是个什么东西?它不是个东西,是一个容器,一个容纳AcceptSAEA对象的容器。给你两个socket服 务器,你能很快判别两个服务器性能的优异吗?很简单,你瞬间向一台服务器打入5、6万的连接,看看会不会都连上,如果都连上,说明这台socket服务器 的并发处理连接的能力还是不错的。那如何提高socket服务器的并发连接能力呢?答案:poolOfAcceptEventArgs!

4)消息队列调度器

消息队列调度器主要分为两种:接受消息队列、发送消息队列。为什么要用消息队列呢?主要是提高socket服务器的吞吐量。首先我们定义一个队列 Queue,然后编写N个调度器,不断从队列中调度消息,接受队列调度器用于将消息抛至业务逻辑层处理,发送队列调度器用于调用网络层发送消息接口,向指 定端口发送数据。

5)心跳扫描

有一个困惑:客户端连接socket服务器,连接没有断开,但客户端挂了,这样这条连接在socket服务器中就成了钉子户,落地生根不走了!一个钉子户 还可以忍受,千千万万个呢?那就崩溃了!怎样解决这个问题呢?定时扫描每条连接,如果该条连接在超时时间内没有IO响应,则关闭它。

6)粘包

服务器在接受消息包时,如果两个数据包同时被你服务器收了怎么办?你会把他当成一个数据包吗?如果一个数据包断了,分成两次被你服务器收了,你会把他们拼接起来吗?这些就是粘包了,怎么解决?正则表达式扫描!

7)多线程编程

Socket服务器的编程就是多线程编程,面对多线程,线程间怎样同步、怎样避免死锁?多线程访问公共资源如何处理,在下面的博文中,我将会为大家具体阐述。

(二) 业务逻辑层

网络层将解包后的消息包抛至业务逻辑层,业务逻辑层收到消息包后,解析消息类型,然后转入相应的处理流程处理。

网络层应提供发送消息的接口供业务逻辑层调用,因为网络层不会主动发送消息,发送消息的操作是由业务逻辑层来控制的,所以业务逻辑层应根据具体的业务应用,封装不同功能的发送消息的方法。

(三) 会话层

会话层主要用于记录在线用户信息,该层隶属于业务逻辑层。既然隶属于业务逻辑层,那为什么还要独立出来呢?这主要为以后分布式开发拓展用,试 想,一台服务器最大能支持多少人同时在线?中国有多少人?如果1亿人同时在线,你一台服务器能支持得了吗?答案肯定是否定的,所以要分布式开发。分布式开 发涉及到用户信息同步的问题,所以会话层就要独立出来了。

(四) 数据访问层

数据库执行效率是整个socket服务器的瓶颈?为什么呢?举个例子:假设我们的socket服务器的秒处理消息的条数为3000,每处理一条 消息都会保存历史记录,那么,如果数据访问层不想拖网络层的后腿,那么他的执行SQL语句的效率也必须达到每秒3000!如果socket服务器和数据库 服务器部署在同一网段上,这个速度是没有问题的,但如果数据库服务器部署在外网呢?你的SQL语句的执行效率能达到那么高吗?很困难!

再思考一个问题:如果网络层执行线程和数据库执行线程是同一个线程,那么网络层的处理必须等待数据库执行完毕后,才能进行!如果数据库执行效率比较慢,那对整个socket服务器将是一个毁灭性的打击。

那么怎样将数据访问层与网络层分离,让他们互不影响?如何提高数据库执行效率,让网络层的处理速度和数据访问层的处理速度达到一个平衡?答案:连接池+sql调度器+主从数据库。

Socket服务器的整体架构就为大家介绍到这里,下面我将会为大家具体阐述各个技术的实现。

[转载]More Effective C# Partial Class是怎样炼成的?

mikel阅读(1014)

[转载]【More Effective C#】Partial Class是怎样炼成的? – 空逸云 – 博客园.

什么是部分类(Partial Class)?

C#中.我们可以利用部分类,将一个类分散到多个类文件中,这样我们就可以多个开发者同时开发某个类库,或者是扩展其他开发者发布的类库.甚至是代码生成器生成的代码,例如LINQ2SQL,ADO.NET EF等,以获取更高效的开发.

Re:Class和Class File的区别.这里的类是我们平时所说的普通类-Class,如抽象类,基类,子类等等.而类文件-Class File则是我们平时编写类时所用到的文件,如C#的.cs,VB的.vb.

虽然部分类很好的解决了多个开发者同时开发而互不影响的问题,但其约束十分严格.开发者之间不能/很难彼此沟通,并且无法/很难修改对方的代码.这意味着需要为对方提供很多挂钩.这些挂钩应该以部分方法的方式实现.而另一方的开发者可以选择实现或不实现.

扩展现有类

实 体框架的出现,大大的减轻了我们的工作量,使我们把更多的重心放在逻辑实现上,而减少对于数据访问的关注.例如我们可以使用 LINQ2SQL,LINQ2SQL是一个轻型的实体框架.它生成了我们对数据层的访问.但.有些时候,我们需要扩展些自己的内容,如方法/属性等等.除 了直接在生成的类文件中添加修改意外,我们还可以利用部分类扩展我们所需的功能,前者当每次我们跟新实体时.你所扩展的内容都会消失.而部分类依然能很好 的为你服务.

提供部分方法

为部分类提供最有效的钩子就是部分方法.它和接口的方法声明很相似.

关键字partial+void+方法名+方法参数.

partial void ReportValueChanging(RequestChange args);

由于部分方法可能成为类的一部分(实现),也可能不成为类的一部分(不实现),所以C#本身对于部分方法给出了一些限制.

1.返回类型必须为void.

2.部分方法不能为抽象/虚方法.

3.部分方法不能用来实现接口.

4.参数列表不能包含任何out参数,因为编译器无法初始化这些out参数.

我们利用部分方法来让用户件事或修改类的行为:修改方法,事件处理程序及构造函数.

修改方法

修改方法是指那些将要修改类中对外可见的状态的方法.我们可以将其理解为任何状态的变化,由于另一个类文件的部分类实现依然是类的一部分.所以我们可以完全控制到该类的所有内部状态.

一般而言,修改方法应该为类提供两个部分方法.第一个在修改前调用,用来让另一方(另一开发者)能够进行合法性验证等等.第二个方法应该在修改状态后调用,用来让另一方相应这个状态的变化.

public partial class GeneratedStuff
    {
        private struct ReportChange
        {
            public readonly int OldValue;
            public readonly int NewValue;

            public ReportChange(int oldValue, int newValue)
            {
                this.OldValue = oldValue;
                this.NewValue = newValue;
            }
        }

        private class RequestChange
        {
            public ReportChange Values
            {
                get;
                set;
            }
            public bool Cancel
            {
                get;
                set;
            }
        }

        partial void ReportValueChanging(RequestChange args);
        partial void ReportValueChanged(ReportChange values);

        private int storage = 0;

        public void UpdateValue(int newValue)
        {
            RequestChange updateArgs = new RequestChange
            {
                Values = new ReportChange(storage, newValue)
            };
            ReportValueChanging(updateArgs);
            if (!updateArgs.Cancel)
            {
                storage = newValue;
                ReportValueChanged(new ReportChange(storage, newValue));
            }
        }
    }

如果另一个部分类没有提供两个部分方法的实现,那么C#编译器将移除该调用.生成方法如下

public void UpdateValue(int newValue)
        {
            RequestChange updateArgs = new RequestChange 
            {
                Values = new ReportChange(this.storage, newValue)
            };
            if (!updateArgs.Cancel)
            {
                this.storage = newValue;
            }
        }

并且,两个部分方法也一并移除了.

我们可以实现挂钩如下

public partial class GeneratedStuff
        {
            partial void ReportValueChanging(GeneratedStuff.RequestChange args)
            {
                if (args.Values.NewValue < 0)
                {
                    //dosomething...
                }
                else
                {
                    //dosomething...
                }
            }

            partial void ReportValueChanged(GeneratedStuff.ReportChange values)
            {
                //dosomething...
            }
        }

这里,我们通过一个取消标记来让开发者可以取消某个修改动作,你也可以通过抛出异常得到同样的效果.但.推荐使用布尔型的取消标记,因为这样更加轻量一些

事件处理程序

实际上,事件的处理程序和上面的方法修改并无多大的区别.你所需要做的.仅仅只是添加一个委托事件而已.实现了类似控件的事件机制.

构造函数扩展

无论是生成的代码,还是我们手工编写的代码.我们都无法预计调用类时外部将会调用哪个构造函数.所以.我们只能在内部做一些手脚.例如

public partial class GeneratedStuff
        {
            partial void Initialize();
            public GeneratedStuff()
                : this(0)
            { }

            public GeneratedStuff(int someValue)
            {
                this.storage = someValue;
                Initialize();
            }
        }

注意!Initialize在最后调用,这样就有了一次检查当前对象状态的机会,可以进行必要的修改,或是对于不符合要求时抛出异常等等.实际 上.这种构造函数重载也有效的提升你的代码质量(对于不同的构造函数,结果IL都会生成不同的函数块.而这种重构法最后其实真正生成的函数块只有一个)

尾声

经过以上的阐述,相信我们已经更了解部分类是怎样炼成的了.只有更好的了解其背后的一系列”小动作”,才有助于我们更好的提升,写出更有质量的代码

[转载]ASP.NET MVC:窗体身份验证及角色权限管理示例

mikel阅读(891)

[转载]ASP.NET MVC:窗体身份验证及角色权限管理示例 – 鹤冲天 – 博客园.

ASP.NET MVC 建立 ASP.NET 基础之上,很多 ASP.NET 的特性(如窗体身份验证、成员资格)在 MVC 中可以直接使用。本文旨在提供可参考的代码,不会涉及这方面太多理论的知识。

本文仅使用 ASP.NET 的窗体身份验证,不会使用它的 成员资格(Membership) 和 角色管理 (RoleManager),原因有二:一是不灵活,二是和 MVC 关系不太。

一、示例项目

image

User.cs 是模型文件,其中包含了 User 类:

public class User
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string Password { get; set; }
    public string[] Roles { get; set;  }
}

UserRepository 为数据存取类,为了演示方便,并没有连接数据库,而是使用一个数组来作为数据源:

public class UserRepository
{
    private static User[] usersForTest = new[]{
        new User{ ID = 1, Name = "bob", Password = "bob", Roles = new []{"employee"}},
        new User{ ID = 2, Name = "tom", Password = "tom", Roles = new []{"manager"}},
        new User{ ID = 3, Name = "admin", Password = "admin", Roles = new[]{"admin"}},
    };

    public bool ValidateUser(string userName, string password)
    {
        return usersForTest
            .Any(u => u.Name == userName && u.Password == password);
    }

    public string[] GetRoles(string userName)
    {
        return usersForTest
            .Where(u => u.Name == userName)
            .Select(u => u.Roles)
            .FirstOrDefault();
    }

    public User GetByNameAndPassword(string name, string password)
    {
        return usersForTest
            .FirstOrDefault(u => u.Name == name && u.Password == password);
    }
}

二、用户登录及身份验证

方式一

修改 AccountController:原有 AccountController 为了实现控制反转,对窗体身份验证进行了抽象。为了演示方便,我去除了这部分(以及注册及修改密码部分):

public class AccountController : Controller
{
    private UserRepository repository = new UserRepository();
    
    public ActionResult LogOn()
    {
        return View();
    }

    [HttpPost]
    public ActionResult LogOn(LogOnModel model, string returnUrl)
    {
        if (ModelState.IsValid)
        {
            if (repository.ValidateUser(model.UserName, model.Password))
            {
                FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
                if (!String.IsNullOrEmpty(returnUrl)) return Redirect(returnUrl);
                else return RedirectToAction("Index", "Home");
            }
            else
                ModelState.AddModelError("", "用户名或密码不正确!");
        }
        return View(model);
    }

    public ActionResult LogOff()
    {
        FormsAuthentication.SignOut();
        return RedirectToAction("Index", "Home");
    }
}

修改 Global.asax:

public class MvcApplication : System.Web.HttpApplication
{
    public MvcApplication()
    {
        AuthorizeRequest += new EventHandler(MvcApplication_AuthorizeRequest);
    }

    void MvcApplication_AuthorizeRequest(object sender, EventArgs e)
    {
        IIdentity id = Context.User.Identity;
        if (id.IsAuthenticated)
        {
            var roles = new UserRepository().GetRoles(id.Name);
            Context.User = new GenericPrincipal(id, roles);
        }
    }
    //...
}

给 MvcApplication 增加构造函数,在其中增加 AuthorizeRequest 事件的处理函数。

代码下载:Mvc-FormsAuthentication-RolesAuthorization-1.rar (243KB)

方式二

此方式将用户的角色保存至用户 Cookie,使用到了 FormsAuthenticationTicket。

修改 AccountController:

public class AccountController : Controller
{
    private UserRepository repository = new UserRepository();
    
    public ActionResult LogOn()
    {
        return View();
    }

    [HttpPost]
    public ActionResult LogOn(LogOnModel model, string returnUrl)
    {
        if (ModelState.IsValid)
        {
            User user = repository.GetByNameAndPassword(model.UserName, model.Password);
            if (user != null)
            {
                FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
                    1,
                    user.Name,
                    DateTime.Now,
                    DateTime.Now.Add(FormsAuthentication.Timeout),
                    model.RememberMe,
                    user.Roles.Aggregate((i,j)=>i+","+j)
                    );                    
                HttpCookie cookie = new HttpCookie(
                    FormsAuthentication.FormsCookieName,
                    FormsAuthentication.Encrypt(ticket));
                Response.Cookies.Add(cookie);

                if (!String.IsNullOrEmpty(returnUrl)) return Redirect(returnUrl);
                else return RedirectToAction("Index", "Home");
            }
            else
                ModelState.AddModelError("", "用户名或密码不正确!");
        }
        return View(model);
    }

    public ActionResult LogOff()
    {
        FormsAuthentication.SignOut();
        return RedirectToAction("Index", "Home");
    }
}

修改 Global.asax:

public class MvcApplication : System.Web.HttpApplication
{
    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);
        }
    }
    //...
}

代码下载:Mvc-FormsAuthentication-RolesAuthorization-2.rar (244KB)

三、角色权限

使用任一种方式后,我们就可以在 Controller 中使用 AuthorizeAttribute 实现基于角色的权限管理了:

[Authorize(Roles = "employee,manager")]
public ActionResult Index1()
{
    return View();
}
[Authorize(Roles = "manager")]
public ActionResult Index2()
{
    return View();
}
[Authorize(Users="admin", Roles = "admin")]
public ActionResult Index3()
{
    return View();
}

四、简要说明

MVC 使用 HttpContext.User 属性进行来进行实现身份验证及角色管理,同样 AuthorizeAttribute 也根据 HttpContext.User 进行角色权限验证。

因些不要在用户登录后,将相关用户信息保存在 Session 中(网上经常看到这种做法),将用户保存在 Session 中是一种非常不好的做法。

也不要在 Action 中进行角色权限判断,应该使用 AuthorizeAttribute 或它的子类,以下的方式都是错误的

public ActionResult Action1()
{
    if (Session["User"] == null) { /**/}
    /**/
}
public ActionResult Action2()
{
    if (User.Identity == null) { /**/}
    if (User.Identity.IsAuthenticated == false) { /**/}
    if (User.IsInRole("admin") == false) { /**/}
    /**/
}

若本文中有错误或不妥之处,敬请指正,谢谢!

[转载]C# DataGridView合计行完整解决方法

mikel阅读(2029)

[转载]C# DataGridView合计行完整解决方法、用户体验深刻、超实用 – 专注实用,代码真实可用 – 博客园.

在网络上看过很多关于DataGridView合计的设计及源码,搜到了很多相同的文章(都是相互复制),一点创新都没有。

普遍存在的问题是用户体现不行,或存在很多BUG等。。

很多的人写的解决办法如下几条:

  1. DataGridView的最后一行作为合计行,每增加或删除一行后重新计算合计行。
  2. 用两个大小相同的DataGridView,一个是数据表格,一个是合计表格。
  3. 第三个是用Table类SUM增加一条合计行数据,再显示到DataGridView中。
  4. 干脆用第三方控件。。。。。。。。

以上出现的问题:

合计行不能显示在DataGridView界面的最下方,用户只能滚条到表格最后才能看到合计。

如果用两个DataGridView,那我只能说他是“天才”,我想性能怎么样,大家可想而知。

如果用Table类SUM一条,同样会产生只能要最后一行看到,如果别人不使用Table显示到DataGridView中怎么办。

上述的都不能很好的解决DataGridView合计行,以下是我个人设计原理与说明:

首先我们解决的是不管用什么方式填充DataGridView数据都能够合计数据。

必须显示在DataGridView界面的最下方,这样用户体验深刻一点。

从上我们可以看到,我们要做到合计行的话,我们必须在表现层(UI)做,不要在逻辑层与数据层做,(如果出错的话只是显示错误)

最终效果如下:

合计我使用的是Lable对象。DataGridView需要处理的事件有单元格的数据改变事件CellValueChanged,滚动条事件,列宽事件,这件事件发生后重绘Lable及计算所需要列数据。

定义对象:Lable对象名为FootSumLabel,DataGridView对象名为DetailDataGrid

代码

//定义类的私有变量,两个是滚动条 HScrollBar hs; VScrollBar vs; //合计数值 decimal fSumQty = 0, fSumCBAmount = 0; //加载数据时先注册DetailDataGrid的滚动条事件,在类的构造时注册
 //DetailDataGrid类中包括有HScrollBar,VScrollBar如下 //hs_ValueChanged,vs_ValueChanged两个事件都重绘FootSumLabel,即FootSumLabel.Invalidate();这一句语句 hs = ((HScrollBar)this.DetailDataGrid.Controls[0]); vs = ((VScrollBar)this.DetailDataGrid.Controls[1]); hs.ValueChanged += new EventHandler(hs_ValueChanged); vs.ValueChanged += new EventHandler(vs_ValueChanged); //DetailDataGrid的CellValueChanged事件调用sumdata(); //FootSumLabel的重绘Paint事件 private void FootSumLabel_Paint(object sender, PaintEventArgs e) { int count = DetailDataGrid.Columns.Count; DataGridViewColumnCollection Columns = this.DetailDataGrid.Columns; Graphics grf = e.Graphics; int x = this.DetailDataGrid.RowHeadersWidth - 2; StringFormat strfmt = new StringFormat(); strfmt.Alignment = StringAlignment.Far; for (int i = 0; i < count; i++) { x += Columns[i].Width; if (i == 4) { int xx = x + Columns[i + 1].Width; grf.DrawString(string.Format("{0:F2}", fSumQty), this.FootSumLabel.Font, Brushes.Black, xx - hs.Value, 3, strfmt); } if (i == 7) { int xx = x + Columns[i + 1].Width; grf.DrawString(string.Format("{0:F2}", fSumCBAmount), this.FootSumLabel.Font, Brushes.Black, xx - hs.Value, 3, strfmt); } } } //合计数值函数 private void sumdata() { fSumQty = 0; fSumCBAmount = 0; DataGridViewRowCollection rows = this.DetailDataGrid.Rows; foreach (DataGridViewRow row in rows) { fSumQty += Convert.ToDecimal(row.Cells["fQty"].Value); fSumCBAmount += Convert.ToDecimal(row.Cells["fAmount"].Value); } //重画 this.FootSumLabel.Invalidate(); }

以上代码可以自行加工成一个类,DataGridView调整列宽的事件只要调用重绘FootSumLabel就可以了。如果有不正之处请指出。

如果要转帖请注明出处:http://www.cnblogs.com/NetWZ/articles/1862097.html 作者:.NetWZ

转载asp.net mvc 部署时出现错误 没有对“C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files”的写访问权限

mikel阅读(878)

转载asp.net mvc 部署时出现错误 没有对“C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files”的写访问权限 – 葡萄家园 – 博客园.

在IIS中 发布程序一个ASP.NET程序,通过IE访问报如下错误:

当前标识(NT AUTHORITY\NETWORK SERVICE)没有对“C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files
说明: 执行当前 Web 请求期间,出现未处理异常。请检查堆栈跟踪信息,以了解有关该错误以及代码中导致错误出处详细信息。

异 常详细信息: System.Web.HttpException: 当前标识(NT AUTHORITY\NETWORK SERVICE)没有对“C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files

=====================================

在“开始”-“运行”里输入如入命令,回车,搞定

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_regiis -i -enable

[转载]android 条码识别软件开发全解析

mikel阅读(1068)

[转载]android 条码识别软件开发全解析 – tankaixiong – 博客园.

Android 条码识别软件开发全解析
Android 的有一大特色就是可以识别条形码得到URL可以直接访问地址并下载,这

个功能无疑为用户提供了很大的方便。

于是在android 上开发条码识别软件肯定是很有必要的!我在网上GOOGLE了一下

发现这类的资料特别少,而且又有很多人在问如何开发。

这里我就分享一下自己的经验。

这里我们会用到一个开源组件zxing ,大体上所有流行的条码都支持(一维码,二维码),并且识别是最好的在所有开源里面。我将详细讲解ZXing的使用
官网地址:http://code.google.com/p/zxing/
这个开源组件支持的条码有如下:
UPC-A and UPC-E
EAN-8 and EAN-13
Code 39
Code 93
Code 128
QR Code
ITF
Codabar
RSS-14 (all variants)
Data Matrix
PDF 417 (‘alpha’ quality)

下面我会详细讲解zxing的使用,将在我下几篇博客中详细讲解。今天又很晚了,

时间总是过的很快啊,尤其是晚上!呵呵,这里先贴出一些效果图吧!
下几篇我的博客文章再详细讲解,大致有如下几个方面:
zxing的编译打包
zxing的简单调用
zxing生成自定义二维码
识别图片中的条码
效果如图:

自定义二维条码

识别图片中的条码:

[转载]关于大型asp.net应用系统的架构-架构的选择

mikel阅读(934)

前言

最近几年在.net方面的工作经历,让我长久以来(有几年了)想写关于大型ASP.NET应用系统架构文章的念头。之前和同事们聊天的时候说的 都是一些思维片段,其中的想法不尽完善,聊完天再仔细想想,一些主意就逐渐清晰了。现在终于付诸行动了,将一些想到的主意与大家一起探讨,也算是对过去几 年在ASP.NET方面的一个总结。这对我来说也是一个学习过程。

博客园有不少同仁在写系统架构或者企业应用架构方面的文章,我看过其中一些。就我看过的这些文章,我发现他们当中相当多的人写的是分层架构。从 我的看法来说,分层是不错。但是如果是我自己写的话,我会从架构的选择来说起。那么应用程序的架构就有可能不选择分层的架构,而选择其他架构。另外我会从 整个系统的角度来写,即从硬件和软件两个角度来思考一个系统。

这些都是我的一些建议,希望对您有所帮助。

简介

大型ASP.NET应用要考虑如何服务众多的访问者,同时还要保证每个访问者都获得高质量的服务。需要面对不同语言的用户;需要保证安全性;应 用系统的伸缩性也是很强的,当服务器集群有点不足以担负压力时,可以向服务器集群中加入更多的服务器来增加整个应用系统的服务能力。服务器的可用性也会要 求很高,一年的下线时间是很少的。服务器的灾难备份也是很好的,即使现在的机房遭受毁灭性打击,也有灾难备份可以恢复服务。服务器上跑的asp.net应 用是可扩展的,具有很好的可扩展性,同时具有良好的可维护性。本系列文章将谈谈大型asp.net应用系统架构的诸多方面。本篇将谈到架构的选择。

架构的选择

架构的选择与应用程序的类型有关。这里说的是asp.net应用,那么Client-Server的架构就很显然排除了。剩下:

基于组件的架构

应用可以按组件划分,不用组件实现不同功能和逻辑,组件之间的接口规范有很好的定义。某些组件可以重用。

分层Layered的架构

应用被划分成了堆叠在一起的若干层,每一层完成特定的服务和功能,与其上下层接口,各层 之间是调用被调用的关系。在最上面的层只有调用下面的一层,在中间的层则兼有调用和被调用。在最下面的层则是仅供上面的层调用。通常划分成UI层,商务逻 辑层,数据层等,并且通常多个层都部署在同一台服务器上。

消息总线型的架构

应用程序按照预定义的格式来收发消息。有一个消息队列和消息存储,分发处理的任务。相关消息的事件被程序处理。支持不同的系统平台。消息总线里面有若干定义好的消息流,消息总线同各系统平台交换数据,支持不同的格式。将消息交由不同的处理程序处理。

Model, View, Controller(MVC)架构

用户交互的处理与UI显示分离

用户交互的处理和UI显示与数据分离

3Tier/N Tier的架构

Tier可以译成排。以与Layer(层)有所区别。将应用程序划分成一系列的服务,包 括UI, Business(商业逻辑), 数据等服务。各Tier可部署在不同的服务器上。类似于分层(layer)的架构。通常分层(layer)不跨机器的边界,也即所有层(layer)都部 署在一台服务器上。Tier是要跨机器的边界。各Tier之间用预定义的通信协议来通信,如WCF, Web service, 或者TCP/IP等。分层(layer)的各层(layer)之间的通信都是通过该编程语言的引用和调用来实现的。所以是有区别的。

面向对象的架构

应用可以划分成自给自足的可重用的对象集合,对象包含了数据和行为。各对象之间有消息交互。

面向服务的架构

应用使用一个功能是通过调用一个服务。在服务提供者和调用者之间有通信合同和消息,通 信合同定义了消息的格式和通信的方式。消息则包含通信的内容。面向服务的架构是“请求-响应”的工作模式。应用程序是以一种服务提供的,调用者需要向服务 发送预定义好的请求消息,服务才做出响应。

这些架构类型都可以用来开发asp.net应用。我们可以从其中选择架构类型的组合来,比如:分层Layered的架构 + 面向服务的架构。MVC架构 + 消息总线型架构。具体的选则,取决于应用程序的要求。现在说一下如何选架构:

如果

  • 有若干现成组件,比如以前系统的ActiveX组件或者.net的组件
  • 应用程序足够简单而不需要分层的架构,通过调用这些组件就可完成大部分工作
  • 不同语言开发的组件需要结合在一起,如ASP.net需要调用VB写的COM+的组件
  • 应用程序需要支持插件技术,可以动态切换组件,例如用.net反射技术实现的插件技术

那么我们可以选择基于组件的架构。

如果

  • 应用程序比较复杂,不同的功能需要不同的层来各司其职,如数据访问,商务逻辑,表现等。
  • 有比较复杂的商务逻辑和流程。

那么我们可以选择分层的架构。

如果

  • 有若干已有系统并且这些系统之间有特定的交互
  • 需要让一个系统与外部的其他系统交互
  • 不同平台上的系统相互之间进行交互

那么我们可以选择消息总线型的架构

如果

  • 要获得分离的UI视图和处理逻辑
  • 要UI视图和处理逻辑与数据存储分离

那么我们可以选择Model,View,Controller(MVC)架构

如果

  • 应用全部在内部网里
  • 应用在互联网上,同时商务逻辑需要暴露给公众使用
  • 商务逻辑足够复杂,需要专门的服务器来提供商务逻辑服务。
  • 应用程序比较复杂,不同的功能分布在不同的服务器上,每一种功能,都可能是由一组服务器来提供。

那么我们可以选择3 Tier/N Tier架构

如果

  • 相关商业领域有足够多的现实对象(这些对象通常是相关商务人员口中的名词),并且这些对象之间有交互
  • 应用比较复杂,需要更多的抽象
  • 对象的数据和行为都需要封装以利重用
  • 有足够的资源来做深入的面向对象分析,如时间,人力等。

那么我们可以选择面向对象的架构。

如果

  • 应用需要支持平台无关性
  • 多个应用程序的功能放进一个单一的界面来提供
  • 采用请求-响应模式运行
  • 需要开发软件加服务(Software plus service),软件即服务(Software as a service)类型的应用,或者基于云计算的应用

那么我们可以选择面向服务的架构。

针对目前的场景:大型ASP.NET应用,那么它最基本的需求可能是这样的:

同时访问的用户将会是相当多的,比如几千个,上万个。

7×24小时都有大量用户访问

某些地方需要用户登录以获取一些需要授权才能获得的信息

我们可能选择的架构组合可能是这样的:

3Tier/N Tier的架构

Model, View, Controller(MVC)架构结合3Tier/N Tier的架构

3Tier/N Tier的架构结合面向服务的架构

3Tier/N Tier的架构结合面向对象的架构

当然也有可能是其他的组合。

分层Layered的架构不适合大型的ASP.NET应用。分层Layered的架构通常将UI层,商务逻辑,数据访问层都部署在同一台服务器 上,首先一台服务器不能负担众多的用户,还有复杂的商务逻辑不是一台服务器能全部担负的。所以分层Layered的架构不适合大型的ASP.NET应用。 小型的ASP.NET应用才适合分层Layered的架构。

基于组件的架构也不适合大型ASP.NET应用。通常来说大型的ASP.NET应用都是相当复杂的,它的UI界面,商务逻辑,数据都是复杂的。 不会简单到调用几个控件就完成了大部分的工作,大型的ASP.NET应用的每一个Tier排,都需要众多的服务器来分担压力,基于组件的架构的分布式能力 有限,所以基于组件的架构是通常不会在大型ASP.NET应用里考虑的,除非是有若干个重要的控件,并且要考虑集成多个编程语言的控件时,才会考虑基于组 件的架构。而且是在某个局部使用,即需要与其他架构一起结合起来用。

消息总线型架构可以在某些场景下参与大型ASP.NET应用的开发。通常是需要将多个系统平台整合在一起的时候。消息总线型的架构需要结合其他的架构来共同构造ASP.NET应用。

MVC架构关注的更多的是UI,用户交互的控制以及数据存取的分离。通常不能单独去构造一个大型的ASP.NET架构。需要结合3Tier/N Tier架构来共同构造大型ASP.NET的架构。MVC架构在UI还有用户交互上有固定的模式,所以可以在UI这一块应用MVC的架构,当涉及到MVC 中的模型Model时,就可以扩展到3 Tier/N Tier的架构。即在访问模型Model时,就去访问另外一个服务器上的商务逻辑和数据存储。这个可以用下图来表示:

面向对象的架构是更多地关注应用里面的面向对象分析,设计等过程产生出来的结果。这个结果体现了现实世界中的对象之间的交互作用。面向对象的架构需要结合其他架构如3 Tier/N Tier架构来共同构造ASP.NET应用程序的架构。

面向服务的架构是在特定场景下需要的。即上面所说的,多个功能作为一项服务,提供一个统一的UI给外界用户。大型ASP.NET应用中通常需要 将商务逻辑提供给公众访问。这时就可以采用面向服务的架构。面向服务的架构也需结合其他架构如3 Tier/N Tier架构来共同构造ASP.NET应用程序的架构。

3 Tier/N Tier架构对于大型ASP.NET应用来说是必须的。它的每一Tier排都由若干服务器组成。只有这样才可以服务众多的用户。如上面的图所示,UI调用商务逻辑时得跨越机器的边界,调用另外一台服务器上的商务逻辑服务接口。

结束语

架构的选择需要根据不同架构的特点和应用程序的需求来进行选择,有时候需要用多个架构的组合才足以满足一个复杂应用的需求。设计者需要根据实际情况来决定合适的架构选择。

接下来将展开谈谈3 Tier/N Tier架构……

[转载]关于大型asp.net应用系统的架构-架构的选择 – Mike的技术博客 – 博客园.

[转载]ASP.NET MVC视图引擎SPARK文档中文版(一)

mikel阅读(1010)

[转载]ASP.NET MVC视图引擎SPARK文档中文版(一) – 黑曜石 – 博客园.

一,前言

写个前言还是不错的,可以先让大家看看这个文章是个大体什么内容,然后打酱油的打酱油,路过的继续路过,但相信停下来总有些许收获。

很久没有写东西,一个最近挺忙,二个好像没有什么特别好的内容好写,三个没有什么很好的心得与大家分享。最近一个项目前台使用MVC,用到了SPARK引 擎,感觉灰常好用,不敢私藏,拿出来给大家分享一下,而又因为关于SPARK的中文资料简直可以说是没有,所以这篇大家可以认为是翻译,只是翻译水平比较 差点,大家凑合了,如果有高手指出不足最好了,抛砖引玉了。

ASP.NET MVC 现在已有四种主要的视图引擎:Spark、NHaml、Razor和传统的ASPX文件模板,Razor是新生事物,传统的ASPX文件模板做过MVC的 大体都有了解和应用了,而Spark和NHaml似乎用得不是很多,很有可能是中文资料欠缺,导致连了解的都不多。本人是在搜索关键词ASP.NET MVC SPARK,百度和GOOGLE都是一篇:《ASP.NET MVC的四种视图引擎》,文章只是粗略的写了一下各个视图的特点,确实是让大家有了一点点的了解。

本文基本上算是Spark官方文档的翻译,其网址是:http://sparkviewengine.com/documentation,最近网络河蟹乱爬,如果打不开,请大家自行翻墙!

二,将Spark加载到ASP.NET MVC
Spark引擎不被controller管理,最简单的方法是在ASP.NET MVC web应用程序中将SparkViewFactory在Global Application_Start中注册,如:
protected void Application_Start(object sender, EventArgs e)
{
ViewEngines.Engines.Add(new SparkViewFactory());
}
这也是注册其它非默认视图引擎的方法

三,Spark配置
1,web.config中的配置
所有的spark配置都包括在<spark>标签中,如:
<!–增加spark配置名–>
<configSections>
<section name=”spark” type=”Spark.Configuration.SparkSectionHandler, Spark”/>
</configSections>
<!–spark配置主体–>
<spark>
<compilation Debug=”true|false” defaultLanguage=”Default|CSharp|VisualBasic”>
<assemblies>
<add assembly=”YourAssemblyName” />
<add assembly=”YourOtherStrongAssemblyName, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35″ />
</assemblies>
</compilation>
<pages automaticEncoding=”true|false” pageBaseType=”Your.NonDefault.BaseSparkView” prefix=”{optional string}”>
<namespaces>
<add namespace=”System”/>
<add namespace=”System.Collections.Generic”/>
<add namespace=”System.Linq”/>
<add namespace=”System.Web.Mvc”/>
</namespaces>
<resources>
<add match=”/content/css” location=”http://www.yourcdnprovider.com/youraccount/allstyles/css”/>
<add match=”/content/js” location=”http://www.yourcdnprovider.com/youraccount/appname/js”/>
</resources>
</pages>
</spark>
自动解码最好是true,这将会对每个使用${expr}的操作符都进行HTML encoding,而使用方法!{expr}将避免被解码
resources区域用于控制形如”~/“样式的链接,如:<script src=”~/content/js/foo.js”/>,将会被解析成给定的location+”/foo.js”,对于使用静态文件或CDN来说是非常有意义的。

2,也可以在Application_Start中配置,如:
protected void Application_Start(object sender, EventArgs e)
{
var settings = new SparkSettings()
.SetDebug(true)
.SetPageBaseType(“Your.NonDefault.BaseSparkView”)
.AddAssembly(“YourAssembly”)
.AddNamespace(“System”)
.AddNamespace(“System.Collections.Generic”)
.AddNamespace(“System.Linq”)
.AddNamespace(“System.Web.Mvc”);

ViewEngines.Engines.Add(new SparkViewFactory(settings));
}

四,常规语法

1,包含代码
可以使用包括C#代码,任何helper方法,对象属性或返回非空值的表达式,如:
<p>${“Hello world”}</p>
<p>${Guid.NewGuid().ToString(“n”)}</p>
代码可以表现为特殊的html标签或其属性,如”if”,”else”和”var”,在这些代码的区域中将不需要使用${},如:
<var names=”new [] {‘alpha’, ‘beta’, ‘gamma’}”/>
<for each=”var name in names”>
<test if=”name == ‘beta'”>
<p>beta is my favorite.</p>
<else/>
<p>${name} is okay too I suppose.
</test>
</for>
但如果你想输出一个普通标签的属性时,标记符号还是需要加上的,如:
<viewdata currentProduct=”Product”/>
<p>${currentProduct.Name} <a href=”/Product/Edit/${currentProduct.Id}”>Edit</a></p>

2,代码中的特殊字符
可以使用所有的C#代码,但是请小心使用,确保你的商业逻辑都写在你的controller或其它代码中
只有很少一部分的代码需要使用特殊的方法去避免被错误解析,如使用[[和]]代替<>,另外,在属性中如果使用了双引号,那么属性中如果使用了spark语法的字符型值时,则需要使用单引号;如果使用单个char常量,使用(char)’x’代替”x”,如:
<viewdata products=”IList[[Products]]”/>
<var styles=”new[] {‘even’, ‘odd’}”/>

3,配置使用Spark前缀
默认情况下,Spark标签是不需要其它特殊标记的,但是当使用Spark生成xml或xhtml时,配置spark前缀就有必要了,方法如下:
<spark>
<pages prefix=”s”/>
</spark>
Or
var settings = new SparkSettings()
.SetPrefix(“s”);
当配置了前缀时,每个spark标签或属性都需要以前缀开头,以被spark引擎识别,而spark内置的标签如macro:,content:,use:和section:不需要加前缀,如:
<div>
<s:viewdata user=”UserInfo”/>
<p s:if=”user != null”>${user.Name}</p>
<s:LoginForm/>
</div>

4,引入xmlns namespaces至Spark标签中
这个不是太好理解,直接上例子:
<html xmlns=”http://www.w3.org/1999/xhtml
xmlns:s=”http://sparkviewengine.com/
xmlns:fn=”http://sparkviewengine.com/macro/“>
<body>
<fn:ShowNames favorite=”string”>
<s:var names=”new [] {‘alpha’, ‘beta’, ‘gamma’}”/>
<s:for each=”var name in names”>
<s:test if=”name == favorite”>
<p>${favorite} is my favorite.</p>
<s:else/>
<p>${name} is okay too I suppose.
</s:test>
</s:for>
</fn:ShowNames>
${ShowNames(“beta”)}
${ShowNames(“gamma”)}
</body>
</html>

五,变量
1,声明局部变量
使用”var“标签可以简单快速的声明局部变量,默认的行为将会是生成一个有初始值的局部动态变量类型”var“,如:
<var styles=”new [] {‘odd’, ‘even’}” i=”0″/>
将产生如下:
var styles = new [] {“odd”, “even”};
var i = 0;
”var“标签有一个”type“属性,可以指明变量的类型,如:
<var foo=”null” bar=”null” type=”string”/>
将产生如下:
string foo = null;
string bar = null;
最后,如果”var“标签没有在一个以”/>“结尾的标签内声明,变量将会有一个作用域,作用域以”</var>“结束,在作用域外不将不能对变量进行赋值,如:
<div>
<var styles=”new [] {‘even’, ‘odd’}” i=”0″>
<p>${i} is ${styles[i%2]}</p>
</var>
<set i=”5″/> <!– compiler error – variable i is out of scope –>
</div>
将产生如下:
Output.Write(“<div>”);
{
var styles=new[] {“even”, “odd”};
var i = 0;
Output.Write(“<p>”);
Output.Write(i);
Output.Write(” is “);
Output.Write(styles[i%2]);
Output.Write(“</p>”);
}
Output.Write(“</div>”);
i = 5;

2,声明全局变量
局部变量有其作用域,而全局变量可以在view,master或局部模板文件中声明,其值可以在任何地方使用,如:
<h2>${Title}</h2>
<global Title=”‘My Default Title'”/>
注意到其值是以双引号包围,内部还有单引号(可以理解为双引号是因为HTML标签属性值要以双引号包含,而内部是string类型,用单引号避免外部双引号重复使用)
全 局变量实际上将会以视图类中的一个字段成员执行,所以上面例子中将将产生一个object Title=”My Default Title”的字段成员,任何方法都可以引用这个变量,spark仍然提供一个”type“属性声明具体类型以避免使用object类型,另外,多个变量 可以声明要同一个标签中,如:
<global type=”string” Foo=”‘Hello'” Bar=”‘World'” />

3,设置局部和全局变量的值
${expr}和其它语法只是用于输出值,局部或全局变量的赋值需要使用”set“标签,如:
<html>
<head>
<global type=’string’ Title='”Site Name”‘/>
<title>${Title}</title>
</head>
<body>
<div><use content=”view”/></div>
</body>
</html>
在视图中设置Title的值
<set Title=’product.Name + ” – ” + Title’/>
<!– or –>
<set Title=’string.Format(“{0} – {1}”, product.Name, Title)’/>

4,使用view data
ViewData作为一个对象字典可以使用形如${ViewData[“blah”]}来使用,但是建议使用一个强类型的ViewData将会使操作更为简便,如:
<viewdata
Caption=”string”
Products=”System.Collections.Generic.IList[[MyApp.Models.Product]]”/>
<div>
<h3>${Caption}</h3>
<div  each=”var product in Products”>
<h4>${product.Name}</h4>
<div>${product.Description}</div>
</div>
</div>
ViewData标签可以声明在view中或是master文件中,结果都是一样的,如:
string Caption
{get {return (string)ViewData[“Caption”];}}

System.Collections.Generic.IList<MyApp.Models.Product> Products
{get {return (System.Collections.Generic.IList<MyApp.Models.Product>)ViewData[“Products”];}}
在这种情况下,需要使用[[and]]来代替<and>

5,使用view data中的确定类型的model
在view或master文件中viewdata可能有一个model的属性
<viewdata model=”MyApp.Models.Catalog”/>
<p>(${ViewData.Model.CatalogID}) ${ViewData.Model.Name} </p>
model 的属性可以和其它viewdata中的变量声明共同使用,例如在master文件中使用了一个<viewdata Title=”string”/>,在view中仍然可以使用含有一个Title属性的string字段的model,<viewdata model=”typename”/>
<viewdata Title=”string”/>
<title>${Html.Encode(Title ?? “My Sample MVC Application”)}</title>
这一段不好理解,建议大家可以看看英文原文

六,表达式
1,简单的C#表达式
可以使用${expr}或<%=expr%>
例:
<div>
<p>The time is ${DateTime.Now}.</p>
<p>The time is <%=DateTime.Now%>.</p>
</div>

2,Nulls表达式
当使用简单的C#表达式时,如果值为null时,将会输出错误,此时可以使用$!{expr}来避免输出错误,只是输出空值
<div class=”shipto”>
<p>$!{currentInvoice.ShipTo.FullName}</p>
<p>$!{currentInvoice.ShipTo.Address.City}, $!{currentInvoice.ShipTo.Address.State} $!{currentInvoice.ShipTo.Address.Zip}</p>
</div>
如果是currentInvoice为null或ShipTo为null时,将会输出如下:
<div class=”shipto”>
<p></p>
<p>,  </p>
</div>
这个主意不是太好,不过总比一个乱码错误页好许多,所以确定你的值100%不为空吧

3,使用MVC helpers
可以使用HtmlHelper,UrlHelper和AjaxHelper
<!– Link to the Sort action on the current controller –>
<p>${Html.ActionLink(“Click me”, “Sort”)}</p>
<!– Put out data in a way that’s safe –>
<p>${Html.Encode(stuff)}</p>

4,条件标签:if,test和else
使用例子说明:
例1:
<var x=’5’/>

<if condition=’x == 5′>
<p class=’resultmessage’>Some value is five</p>
</if>
例2:
<viewdata user=’UserInfo’/>

<if condition=’!user.IsLoggedIn()’>
<p>Here’s a login form</p>
</if>
<else if=’user.HasRole(RoleType.Administrator)’>
<p>Hello – you’re an admin</p>
</else>
<else if=’user.HasRole(RoleType.Registered)’>
<p>Hello – you’re a registered user</p>
</else>
<else>
<p>I have no idea what type of person you are</p>
</else>
例3:
可以使用<test if=””>语法来代替<if condition=””>语法,<else if=””/>和<else/>来代替分散标签
<viewdata user=’UserInfo’/>

<test if=’!user.IsLoggedIn()’>
<p>Here’s a login form</p>
<else if=’user.HasRole(RoleType.Administrator)’/>
<p>Hello – you’re an admin</p>
<else if=’user.HasRole(RoleType.Registered)’/>
<p>Hello – you’re a registered user</p>
<else/>
<p>I have no idea what type of person you are</p>
</test>

5,使用if和elseif判断属性或标签显示
if和elseif可以使用在任何标签中
例1:
<var x=’5’/>
<p if=’x==5′ class=’resultmessage’>Some value is five</p>
例2:
<use namespace=’SampleApp.Models’/>
<viewdata user=’UserInfo’/>

<p if=’!user.IsLoggedIn()’>Here’s a login form</p>
<p elseif=’user.HasRole(RoleType.Administrator)’>Hello – you’re an admin</p>
<p elseif=’user.HasRole(RoleType.Registered)’>Hello – you’re a registered user</p>
<else>
<p>I have no idea what type of person you are</p>
</else>

6,一次判断
在页面中只需要调入一次某个资源,如css文件或JQuery,可标签中使用once=”flagname”还指明,你当然可以使用${expr}或其它语法来为flagname赋值,实际上在spark文件的任何地方都可以使用这样的表达式
<content name=”head”>
<!–  add JQuery if it hasn’t been yet, and add countdown plugin –>
<script once=”JQuery” type=”text/JavaScript” src=”~/content/js/jQuery-1.2.6.js”/>
<script once=”jQuery-countdown” type=”text/JavaScript” src=”~/content/js/jquery.countdown.js”/>
</content>
这里不是太明白

7,有条件的属性输出
使用?{boolean}表达式只能用在属性值中,例:
<ul>
<li each=”var product in Products” class=”first?{productIsFirst} last?{productIsLast}”>
${H(product.Name)}
</li>
</ul>
将会输出
<ul>
<li class=”first”>Alpha</li>
<li>Beta</li>
<li>Gamma</li>
<li class=” last”>Delta</li>
</ul>
or
<ul>
<li class=”first last”>just one product</li>
</ul>
如果属性值为空,那么将表现如下:
<input type=”checkbox” name=”chkhello” checked=”?{isHelloChecked}”></input>
将会输出
<input type=”checkbox” name=”chkhello” checked=””></input>
or
<input type=”checkbox” name=”chkhello”></input>

8,循环
一个foreach循环包含标签for和each属性,each属性值必须包含类型,变量名,关键字”in”和循环体(collection),如:
<viewdata Posts=”IList[[MyApp.Models.Post]”/>
<for each=”var post in Posts”>
<p>${post.Title}</p>
</for>
任何一个循环都有Index,Count,IsFirst和IsLast属性,在循环作用域内有效,而且只有被使用了才会生成,如:
<viewdata Posts=”IList[[MyApp.Models.Post]”/>
<var styles=”new[] {‘even’,’odd’}”/>
<for each=”var post in Posts”>
<p class=”${styles[postIndex%2]}”>${postIndex}. ${post.Title}</p>
</for>
在循环时,如果使用了Count或IsLast变量,spark将会使用IEnumerable<T>的Linq方法后,使用Count()取得,而旧的spark引擎会先循环一次以取得count,所以使用Count和IsLast时,请注意性能问题
以下是局部变量和循环的复杂使用
<viewdata currentProductId=”int”/>
<var styles=”new [] {‘even’, ‘odd’}” isCurrent=”false”>
<for each=”var product in Products” isCurrent=”product.Id==currentProductId”>
<p class=”highlighted?{isCurrent} ${styles[productIndex%2]}”>${Html.Encode(product.Name)}</p>
</for>
</var>

9,在标签属性中使用循环
例1:
<table>
<tr>
<td>Name</td>
<td>Type</td>
</tr>
<tr each=’var user in users’>
<td>${user.Name}</td>
<td>${user.UserType}</td>
</tr>
</table>
例2:
<table>
<tr>
<td>Name</td>
<td>Type</td>
</tr>
<var classes=”new [] {‘even’,’odd’}”>
<tr each=”var user in users” class=”${classes[userIndex%2]}”>
<td>${userIndex}) ${user.Name}</td>
<td>${user.UserType}</td>
</tr>
</var>
</table>

10,使用命名空间
<use namespace=”System.Collections.Generic”/>
<use namespace=”System.Web.Mvc”/>
<viewdata Names=”IList[[string]]”/>
不好说明

11,内置代码
使用”#“符号声明当前行为C#代码,如:
<test if=”user.IsLoggedIn()”>
<p>Hello, ${user.Name}.</p>
<else if=”user.HasValidTrialSession()”/>
<p>Hello, Valued Future Customer.</p>
<else/>
<p>Hello, er… you.</p>
# System.Diagnostic.Trace.WriteLine(“Unexpected anon user.”);
# if (System.Diagnostic.Debugger.IsAttached)
#   System.Diagnostic.Debugger.Break();
</test>
这里需要说明的是spark变量和C#变量是可以通用的!

12,声明宏
当需要一个helper方法时,如需要输入一些参数和返回一个字符串时,<macro>就可以做这些事情了,声明时使用形如<macro name=”foot”>,然后使用时用${foo()}或<%=foo()%>,例:
<viewdata errorMessage=”string” />

<macro name=”ShowError” caption=”string” message=”string”>
<div class=”message error”>
<h3>${H(caption)}</h3>
<div>${message}</div>
<% Logger.Warn(caption); %> <!– this is a MR example. ASP.NET MVC would use different logging –>
</div>
</macro>

<h2>Place Order</h2>
<test if=”!string.IsNullOrEmpty(errorMessage)”>
${ShowError(“Failed to place order”, errorMessage)}
</test>
将会产生如下:
string ShowError(string caption, string message)
{
using(OutputScope(new System.IO.StringWriter()))
{
Output.Write(“\r\n  <div class=\”message error\”>\r\n    <h3>”);
Output.Write(H(caption));
Output.Write(“</h3>\r\n    <div>”);
Output.Write(message);
Output.Write(“</div>\r\n    “);
Logger.Warn(caption);
Output.Write(”  </div>\r\n”);
return Output.ToString();
}
}

//… and in the RenderViewContent method
Output.Write(“\r\n<h2>Place Order</h2>\r\n”);
if (!string.IsNullOrEmpty(errorMessage))
{
Output.Write(ShowError(“Failed to place order”, errorMessage));
} // if (!string.IsNullOrEmpty(errorMessage))
Output.Write(“\r\n\r\n<!– form here, field validation messages, etc. –>\r\n\r\n”);

太多了,今天先写到这,觉得不错,麻烦推荐一个

[转载]ASP.NET MVC:Attribute与TypeDescriptor的故事

mikel阅读(1104)

[转载]Attribute与TypeDescriptor的故事 – 阿不 – 博客园.

前因

使用ASP.NET MVC时,我们必不可少的要与Attribute打交道,利用Attribute来做元数据的定义是一种非常老套的方法,但是相对于其方便快捷以及低廉的 维护成本,还是在MVC框架中得到了充分的运用,主要是用于定义ModelMetadata。在现在的开发框架中,Attribute处理一个非常重要的 地位。但是在使用Attribute过程,还是重复遇到了几回相同的问题,但由于缺少记录,让我重复花了好多时间来进行排查。

话说,我定 义了一个Attribute类型,在一个类上面贴两个不同实例且属性值不同的两个Attribute,但是通过MVC的 ModelMetadataProvider得到的却有一个实例。印象中曾经解决过该问题,但是始终无法记起是何原因还是花了很多时间去调试。其实如果幸 运的话,可以通过网上找到答案,我们只要重写Attribute.TypeId这个属性,让它返回当前对象的实例即可:

1 public override object TypeId
2 {
3 get
4 {
5 return this;
6 }
7 }

分析

很简单的解决方法,但是究竟是何原因呢?还没有其它的更多的故事呢?我还可以进行以下的试验:

我们先定义一个Attribute类Attribute1 ,包含一个Name属性:

1 [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
2 public class Attribute1 : Attribute
3 {
4 public string Name { get; set; }
5 }

接下来,我们再定义一个简单类型,给它贴上两个Attribute1的实例,分别给Name赋不同的值:

1 [Attribute1(Name = "a1")]
2 [Attribute1(Name = "a2")]
3 public class Class2
4 {
5
6 }

接下来我们分别用两种方法来获取Attribute的实例:

  1. 使用标准的反射,用Attribute.GetCustomAttributes来获取自定义Attribute实例
  2. 使用TypeDescriptor.GetAttributes方法来获取Attributes实例,这也是MVC里面所使用的方法。
1 Console.WriteLine(Attribute.GetCustomAttributes(typeof(Class2), typeof(Attribute1)).Count());
2 Console.WriteLine(TypeDescriptor.GetAttributes(typeof(Class2)).Count);

我们的目的主是想要测试,我们通过两种方法所取得的Attribute实例的数量有何差别。当然我按照上面的代码,写一个简单的控制台代码,你得到的结果会是:2和1。也就是,我们通过标准的方法可能得到我们所预期的结果:2;但是通过TypeDescriptor,我们得到的却只有一个实例,重现了我们的问题。

继续下面的测试,我们回头修改一下Attributes1的定义,我们按照先前的解决方案,重写一下TypeId的实现:

01 [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
02 public class Attribute1 : Attribute
03 {
04 public string Name { get; set; }
05 public override object TypeId
06 {
07 get
08 {
09 return this;
10 }
11 }
12 }

不改面其它代码,运行输出测试结果,我们得到的结果会是:2和2。也就是它们得到的结果与我们的期望结果是一致的。如果此时停止测试,那么有可能在以后的使用过程当中我们还会重新陷入这个问题的泥潭,因为还有我们不一定知道的事情。

修改一下Class2的定义,我们让两个Attribute的实例的Name属性使用相同的值:

1 [Attribute1(Name = "a1")]
2 [Attribute1(Name = "a1")]
3 public class Class2
4 {
5
6 }

此时的结果又变回了2和1了。看似也是个合理的结果,因为虽然是两个不同实例的Attribute1,但是它们实际所包含的信息确实是相同的,因此认为它们是相同的对象也不为过。但是这种理解却与我们对对象的相同性的认识却有不同,稍有不慎,还是会给我们带来一些麻烦。

结论

以上的试验告诉我们,如何可以避免类似问题的发生。虽然在MSDN的文档有说明,TypeId是Attribute的唯一标识,但是仅凭这一句话还是没有办法来解决我们所遇到的问题。翻开源码,我们来看看TypeId的实现:

1 public virtual object TypeId
2 {
3 get
4 {
5 return base.GetType();
6 }
7 }

它用类型的实例来做为Attribute的唯一ID,这样虽然我们有不同的Attribute实例,但是得到的TypeId都是一样的。而当我们使 用TypeDescriptor来读取Attributes时,它对根据TypeId对所有的Attribute进行一次过滤,滤去相同TypeId多个 不同实例,只保留一个实例,类似于SQL的distinct关键字。更为隐蔽的是,它还会检查Attribute的所有的属性值,如果全部相同,它也会认 为是相同的Attribute实例,而被过滤掉,而目前,我也还不知道如何解决这个问题。

阿不 http://hjf1223.cnblogs.com

[转载]ASP.NET MVC 3 Beta初体验之实用的WebMail

mikel阅读(911)

[转载]ASP.NET MVC 3 Beta初体验之实用的WebMail – 海纳百川 – 博客园.

ASP.NET MVC 3 Beta中提供了非常实用发送邮件的组件:WebMail。我试用了一下,和System.Web.Mail类似。这篇文章将简单介绍一下这个组件的使 用。通过分成不带附件的邮件发送和带附件的邮件发送两种情况进行讲解。用一个请求帮助的应用场景为例。

不带附件的邮件发送

首先定义Controller。EmailRequest用于请求一个发送邮件的页面,ProcessRequest用去处理发送邮件的请求,并在View中发送邮件。

代码

[HttpGet] public ActionResult EmailRequest() { return View(); } [HttpPost] public ActionResult ProcessRequest() { return View(); }

EmailRequest.cshtml代码如下:

代码

<!DOCTYPE html> <html> <head> <title>求助中心</title></head><body> <h2>发送邮件求助</h2> <form method="post" action="ProcessRequest"> <div>你的姓名: <input type="text" name="customerName" /> </div> <div>你的问题描述: <br /> <textarea name="customerRequest" cols="45" rows="4"> </textarea> </div> <div> <input type="submit" value="Submit" /> </div> </form> </body> </html>

发送邮件的View:

@{ var customerName = Request["customerName"]; var customerRequest = Request["customerRequest"]; try { // 初始化 WebMail.SmtpServer = "smtp.126.com"; WebMail.SmtpPort = 25; WebMail.EnableSsl = false; WebMail.UserName = "zhuqi0"; WebMail.From = "zhuqi0@126.com"; WebMail.Password = "**********"; // 发送邮件 WebMail.Send(to:"zhuqi0@126.com", subject: "来自 - " + customerName+"的求助", body: customerRequest ); } catch (Exception ex ) { <text> <b>邮件发送<em>失败</em></b> 代码中没有提供正确的SMTP服务名,用户名,密码等信息。 </text> } } <!DOCTYPE html> <html><head> <title>求助中心</title></head><body> <p>非常抱歉听到你有麻烦, <b>@customerName</b>. </p> <p>关于下面问题的邮件已经发送给我们的客服,相关部门会及时处理。</p> <p><b>@customerRequest</b></p></body></html>

运行:

发送成功页面

邮件通知:

带附件的邮件发送:

带附件的邮件发送类似,不过需要知道附加地址的列表,发送邮件的带附件的邮件代码如下:

@{ var customerName = Request["customerName"]; var subjectLine = Request["subjectLine"]; var fileAttachment = Request["fileAttachment"]; try { // 初始化 WebMail.SmtpServer = "smtp.126.com"; WebMail.SmtpPort = 25; WebMail.EnableSsl = false; WebMail.UserName = "zhuqi0"; WebMail.From = "zhuqi0@126.com"; WebMail.Password = "**********"; // 创建包含附件的数组 var filesList = new string [] { fileAttachment }; // 添加附件和发送邮件 WebMail.Send(to: "zhuqi0@126.com",subject: subjectLine, body: "File attached. <br />From: " + customerName, filesToAttach: filesList); } catch (Exception ex) { <text> <b>邮件发送<em>失败</em></b> 代码中没有提供正确的SMTP服务名,用户名,密码等信息。 </text> } } <!DOCTYPE html> <html> <head> <title>求助中心</title> </head> <body> <p><b>@customerName</b>, 感谢你的支持.</p> <p>关于下面问题的邮件已经发送给我们的客服,相关部门会及时处理。 <b> @fileAttachment</b> file attached.</p> </body> </html>

从上面的两种情况我们可以看到,WebMail和System.Web.Mail使用的方式是一样的,不过在ASP.NET MVC 3 Beta中WebMail使用起来更简便了。

第一步:初始化,指定邮件发送服务器。

WebMail.SmtpServer = “smtp.126.com”;

第二步:指定端口。

WebMail.EnableSsl = false;

第三步:指定用户名。

WebMail.UserName = “zhuqi0”;

第四步:你的邮箱地址和密码。

WebMail.From = “zhuqi0@126.com“;
WebMail.Password = “********”;

第五步:如果有附件指定附件地址。

var filesList = new string [] { fileAttachment };

第六步:邮件发送。

WebMail.Send(to: “zhuqi0@126.com”,subject: subjectLine,
body: “File attached. <br />From: ” + customerName,
filesToAttach: filesList);

总结:本文简单介绍了一下ASP.NET MVC 3 Beta中WebMail的使用。

代码:http://files.cnblogs.com/zhuqil/MvcApplicationWebMail.rar

作者:朱祁林
出处:http://zhuqil.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。