[MVC]MVC项目在IIS6上部署的几种思路

mikel阅读(859)

转载:http://www.cnblogs.com/downmoon/archive/2009/11/03/1595284.html

  微软在vista和win2008下集成了IIS7,与IIS6相比,有重大升级,看IIS7的十大改进,http://blogs.msdn.com/cqwang/archive/2007/09/10/iis7.aspx

  而在2008年发布的MVC正式版中支持IIS6\IIS7。不过对iis6支持的有点变态。

   下面是邀月的一次正式部署MVC项目到IIS6的过程。为了项目接近实用,在演示项目中引用了一个Web service(主要根据地区邮编或地区名称来获取天气预报)http://www.webservicex.net/WeatherForecast.asmx?WSDL 

  第一步: 正式创建一MVC项目,本项目沿用上个示例,名称为MvcAppDemoFck。项目结构如下图:

  邀月工作室

  第二步:添加web引用,这个与普通的web service没什么不同,输入地址 http://www.webservicex.net/WeatherForecast.asmx?WSDL 

   其他默认即可。如上图。

  第三步:在Controller下添加新的Controller,名称为WeatherController,添加代码如下:

Code

   并在 "public ActionResult Index{"处右键 Add View,新增一个View名称为GetWeather.aspx,即默认名称

  第四步:修改默认的MainContent的内容部分为:

Code

   注意,这里一个端口号为50002,是iis的端口,而不是vs2008内置的端口,

  看图:

   邀月工作室

  下面开始正式的部署:如果此时在浏览器中输入http://localhost:50002/,会提示404错误。出错原因如下:

  This happens because IIS 6 only invokes ASP.NET when it sees a “filename extension” in the URL that’s mapped to aspnet_isapi.dll (which is a C/C++ ISAPI filter responsible for invoking ASP.NET). Since routing is a .NET IHttpModule called UrlRoutingModule, it doesn’t get invoked unless ASP.NET itself gets invoked, which only happens when aspnet_isapi.dll gets invoked, which only happens when there’s a .aspx in the URL. So, no .aspx, no UrlRoutingModule, hence the 404.

  大意是:因为IIS6只对aspx文件才调用 aspnet_isapi.dll进行处理,而在默认的MVC设置里是无路径的,所以会出现404。有人说,为什么vs2008内建的浏览器会支持呢?那正是因为调用了aspnet_isapi.dll的缘故。所以我们在IIS6中只要通知iis,需要对该网站的地址url进行解析处理即可。

   第一种尝试:Use a wildcard mapping for aspnet_isapi.dll,为aspnet_isapi.dll启用通配符映射。这个翻译很别扭。^_^

   iis-网站-属性-主目录-配置-映射-插入–(注意不是添加扩展名),可执行文件名一般为C:\WINDOWS\Microsoft.NET \Framework\v2.0.50727\aspnet_isapi.dll。 去掉"确认文件是否存在"的勾,即不选。

  OK! 再打开浏览器, http://localhost:50002/,似乎一切都很美好!!但这就等于告诉IIS,每一次的请求,都要通过aspnet_isapi.dll,而无论是否是aspx文件。这不是我们想要的。

  第二种尝试:Put .aspx in all your route entries’ URL patterns。在URL格式中设置.aspx路由入口,即在Global.asax.cs文件的RegisterRoutes方法中修改默认的路由规则为

 routes.MapRoute("Default", "{controller}.aspx/{action}/{id}", new { controller = "Home", action = "Index", id = "" });
  这样一来,这样相当于欺骗iis6,告诉IIS6 ,我是aspx文件,你处理一下我吧。

  这样处理的结果就是地址栏变成了 http://localhost:50002/Home.aspx/About或http://localhost:50002/Weather.aspx,但浏览一朷正常。就是地址太恶心了些。

  第三种尝试:Use a custom filename extension in all your URL patterns,在所有的url规则中添加一个可定制的扩展名如mvc等。

    与第一种非常类似。iis-网站-属性-主目录-配置-映射-添加–(注意不是插入),可执行文件名一般为C:\WINDOWS \Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll,扩展名为.mvc,动作限制为 “GET,HEAD,POST,Debug",同样 去掉"确认文件是否存在"的勾,即不选。因为这些文件并不是真正存在的,只不过为了欺骗可怜的IIS6。其实与第二种的效果类似,只是可以定制后缀名, 如".org"等。展示效果同第二种。

  当然,第二种可以用.mvc。同时注意,第二种与第三种必须添加默认Root路径的路由规则,否则会找不到默认主页

 routes.MapRoute("Root"""new { controller = "Home", action = "Index", id = "" });

  出错信息如下:

  邀月工作室 

   如果vs2008中启动调试也会在这句出错。

  第四种尝试:利用第三方URL组件。

  1、发现原来用的ISAPI_Rewrite不能在MVC环境正常使用。可能版本过低,是1.3Full。

  2、老赵推荐使用IIRF(Ionics Isapi Rewrite Filter)开源,地址为:http://iirf.codeplex.com/

官方提供了MVC的匹配规则:http://cheeso.members.winisp.net/Iirf20Help/html/1ccbf1ec-0984-49d9-9ab0-63eab3ff9c63.htm

Code

  按照官方文档,完成配置,需要在iis中设置,而且需要IsapiRewrite4.ini和IsapiRewrite4.dll在同一个目录下,还需要重启iis,希望你看到这些不要崩溃!

   安装见官方地址http://cheeso.members.winisp.net/Iirf20Help/html/c76efb95-05ba-4383-8022-7eff3e1174d0.htm

   配置http://cheeso.members.winisp.net/Iirf20Help/frames.htm
  性能如何,没有测试。

  配置成功,截图如下:

  邀月工作室
  邀月工作室

   第五种尝试:Upgrade to Windows Server 2008 and IIS 7。据说IIS7对MVC提供了完美的支持。财力短缺,在此略去。

  至此,一次mvc项目的部署告一段落。 希望有人能提供更好的方案。

结束语:MVC最好的支持应该是IIS7(vista/win2008)或者是IIS7.5(Win7/win2008r2),这是产品的生命周期使然。另外在部署过程中js的路径比较特殊,除了使用特殊的

Url.Content("~/Content/Js/jquery-1.3.2.min.js")

 外,最好将js文件放置在Content\js下。而不是默认的Scripts文件。其中出过几次错,特别是在第三方URL组件时更易错。

  本文参考的文章:

1、ASP.NET MVC on IIS 6 Walkthrough  (Phil Haack)
2、Deploying ASP.NET MVC to IIS 6 (Steve Sanderson)
3、JQuery AJAX with ASP.NET MVC  (Farooq Kaiser)CodeProject
4、ASP.NET MVC Framework体验(5):路径选择(URL Routing)  (TerryLee)
5、重提URL Rewrite(2):使用已有组件进行URL Rewrite  (JeffreyZhao)
6、Asp.net MVC项目的部署(一):IIS以及Asp.net与IIS相关的部分  (xuefly)
7、Using ASP.NET MVC with Different Versions of IIS  (MVC官网)
8、ASP.NET MVC URL Routing 学习  (QLeelulu)
9、ASP.NET MVC 入门3、Routing    (QLeelulu)
助人等于自助!   3w@live.cn

[MVC]ASP.NET MVC ACTION FILTER Caching Compression

mikel阅读(480)

Caching plays a major role in developing highly scalable web applications. We can cache any http get request in the user browser for a predefined time, if the user request the same URL in that predefined time the response will be loaded from the browser cache instead of the server. You can archive the same in ASP.NET MVC application with the following action filter:

using System;
using System.Web;
using System.Web.Mvc;
public class CacheFilterAttribute : ActionFilterAttribute
{
/// <summary>
    /// Gets or sets the cache duration in seconds. The default is 10 seconds.
    /// </summary>
    /// <value>The cache duration in seconds.</value>
    public int Duration
{
get;
set;
}
public CacheFilterAttribute()
{
Duration = 10;
}
public override void OnActionExecuted(FilterExecutedContext filterContext)
{
if (Duration <= 0) return;
HttpCachePolicyBase cache = filterContext.HttpContext.Response.Cache;
TimeSpan cacheDuration = TimeSpan.FromSeconds(Duration);
cache.SetCacheability(HttpCacheability.Public);
cache.SetExpires(DateTime.Now.Add(cacheDuration));
cache.SetMaxAge(cacheDuration);
cache.AppendCacheExtension("must-revalidate, proxy-revalidate");
}
}

You can apply the filter in your Controller action method like the following.

[CacheFilter(Duration = 60)]
public void Category(string name, int? page)

The following shows the screen-shot in firebug  when cache filter is not applied:

and this is the screen-shot when the cache filter is applied:

Another important thing is compression. Now a days, all modern browsers accept compressed contents and it saves huge bandwidth. You can apply the following action filter to compress your response in your  ASP.NET MVC application:

using System.Web;
using System.Web.Mvc;
public class CompressFilter : ActionFilterAttribute
{
public override void OnActionExecuting(FilterExecutingContext filterContext)
{
HttpRequestBase request = filterContext.HttpContext.Request;
string acceptEncoding = request.Headers["Accept-Encoding"];
if (string.IsNullOrEmpty(acceptEncoding)) return;
acceptEncoding = acceptEncoding.ToUpperInvariant();
HttpResponseBase response = filterContext.HttpContext.Response;
if (acceptEncoding.Contains("GZIP"))
{
response.AppendHeader("Content-encoding", "gzip");
response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
}
else if (acceptEncoding.Contains("DEFLATE"))
{
response.AppendHeader("Content-encoding", "deflate");
response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress);
}
}
}

Just decorate your controller action with this filter:

[CompressFilter]
public void Category(string name, int? page)

The following shows when compression is not applied:

and this is the screen-shot when the compress filter is applied:

You can also apply both these filter in the same action method, like the following:

[CompressFilter(Order = 1)]
[CacheFilter(Duration = 60, order = 2)]
public void Category(string name, int? page)

And this is the screen-shot:

Enjoy!!!

Download: Source.zip

[MVC]ASP.NET MVC View Model Patterns

mikel阅读(438)

转载:http://geekswithblogs.net/michelotti/archive/2009/10/25/ASP.NET-mvc-view-model-patterns.aspx

Since MVC has been released I have observed much confusion about how best to construct view models. Sometimes this confusion is not without good reason since there does not seem to be a ton of information out there on best practice recommendations.  Additionally, there is not a “one size fits all” solution that acts as the silver bullet. In this post, I’ll describe a few of the main patterns that have emerged and the pros/cons of each. It is important to note that many of these patterns have emerged from people solving real-world issues.

Another key point to recognize is that the question of how best to construct view models is *not* unique to the MVC framework.  The fact is that even in traditional ASP.NET web forms you have the same issues.  The difference is that historically developers haven’t always dealt with it directly in web forms – instead what often happens is that the code-behind files end up as monolithic dumping grounds for code that has no separation of concerns whatsoever and is wiring up view models, performing presentation logic, performing business logic, data access code, and who knows what else.  MVC at least facilitates the developer taking a closer look at how to more elegantly implement Separation of Concerns.

Pattern 1 – Domain model object used directly as the view model

Consider a domain model that looks like this:

   1:  public class Motorcycle
   2:  {
   3:      public string Make { get; set; }
   4:      public string Model { get; set; }
   5:      public int Year { get; set; }
   6:      public string VIN { get; set; }
   7:  }

When we pass this into the view, it of course allows us to write simple HTML helpers in the style of our choosing:

   1:  <%=Html.TextBox("Make") %>
   2:  <%=Html.TextBoxFor(m => m.Make) %>

And of course with default model binding we are able to pass that back to the controller when the form is posted:

   1:  public ActionResult Save(Motorcycle motorcycle)

While this first pattern is simple and clean and elegant, it breaks down fairly quickly for anything but the most trivial views. We are binding directly to our domain model in this instance – this often is not sufficient for fully displaying a view.

Pattern 2 – Dedicated view model that *contains* the domain model object

Staying with the Motorcycle example above, a much more real-world example is that our view needs more than just a Motorcycle object to display properly.  For example, the Make and Model will probably be populated from drop down lists. Therefore, a common pattern is to introduce a view model that acts as a container for all objects that our view requires in order to render properly:

   1:  public class MotorcycleViewModel
   2:  {
   3:      public Motorcycle Motorcycle { get; set; }
   4:      public SelectList MakeList { get; set; }
   5:      public SelectList ModelList { get; set; }
   6:  }

In this instance, the controller is typically responsible for making sure MotorcycleViewModel is correctly populated from the appropriate data in the repositories (e.g., getting the Motorcycle from the database, getting the collections of Makes/Models from the database).  Our Html Helpers change slightly because they refer to Motorcycle.Make rather than Make directly:

   1:  <%=Html.DropDownListFor(m => m.Motorcycle.Make, Model.MakeList) %>

When the form is posted, we are still able to have a strongly-typed Save() method:

   1:  public ActionResult Save([Bind(Prefix = "Motorcycle")]Motorcycle motorcycle)

Note that in this instance we had to use the Bind attribute designating “Motorcycle” as the prefix to the HTML elements we were interested in (i.e., the ones that made up the Motorcycle object).

This pattern is simple and elegant and appropriate in many situations. However, as views become more complicated, it also starts to break down since there is often an impedance mismatch between domain model objects and view model objects.

 

Pattern 3 – Dedicated view model that contains a custom view model entity

As views get more complicated it is often difficult to keep the domain model object in sync with concerns of the views.  In keeping with the example above, suppose we had requirements where we need to present the user a checkbox at the end of the screen if they want to add another motorcycle.  When the form is posted, the controller needs to make a determination based on this value to determine which view to show next. The last thing we want to do is to add this property to our domain model since this is strictly a presentation concern. Instead we can create a custom “view model entity” instead of passing the actual Motorcycle domain model object into the view. We’ll call it MotorcycleData:

   1:  public class MotorcycleData
   2:  {
   3:      public string Make { get; set; }
   4:      public string Model { get; set; }
   5:      public int Year { get; set; }
   6:      public string VIN { get; set; }
   7:      public bool AddAdditionalCycle { get; set; }
   8:  }

This pattern requires more work and it also requires a “mapping” translation layer to map back and forth between the Motorcycle and MotorcycleData objects but it is often well worth the effort as views get more complex.  This pattern is strongly advocated by the authors of MVC in Action (a book a highly recommend).  These ideas are further expanded in a post by Jimmy Bogard (one of the co-authors) in his post How we do MVC – View Models. I strongly recommended reading Bogard’s post (there are many interesting comments on that post as well). In it he discusses approaches to handling this pattern including using MVC Action filters and AutoMapper (I also recommend checking out AutoMapper).

Let’s continue to build out this pattern without the use of Action filters as an alternative. In real-world scenarios, these view models can get complex fast.  Not only do we need to map the data from Motorcycle to MotorcycleData, but we also might have numerous collections that need to be populated for dropdown lists, etc.  If we put all of this code in the controller, then the controller will quickly end up with a lot of code dedicated just to building the view model which is not desirable as we want to keep our controllers thin. Therefore, we can introduce a “builder” class that is concerned with building the view model.

   1:  public class MotorcycleViewModelBuilder
   2:  {
   3:      private IMotorcycleRepository motorcycleRepository;
   4:   
   5:      public MotorcycleViewModelBuilder(IMotorcycleRepository repository)
   6:      {
   7:          this.motorcycleRepository = repository;
   8:      }
   9:   
  10:      public MotorcycleViewModel Build()
  11:      {
  12:          // code here to fully build the view model 
  13:          // with methods in the repository
  14:      }
  15:  }

This allows our controller code to look something like this:

   1:  public ActionResult Edit(int id)
   2:  {
   3:      var viewModelBuilder = new MotorcycleViewModelBuilder(this.motorcycleRepository);
   4:      var motorcycleViewModel = viewModelBuilder.Build();
   5:      return this.View();
   6:  }

Our views can look pretty much the same as pattern #2 but now we have the comfort of knowing that we’re only passing in the data to the view that we need – no more, no less.  When the form is posted back, our controller’s Save() method can now look something like this:

   1:  public ActionResult Save([Bind(Prefix = "Motorcycle")]MotorcycleData motorcycleData)
   2:  {
   3:      var mapper = new MotorcycleMapper(motorcycleData);
   4:      Motorcycle motorcycle = mapper.Map();
   5:      this.motorcycleRepository.Save(motorcycle);
   6:      return this.RedirectToAction("Index");
   7:  }

Conceptually, this implementation is very similar to Bogard’s post but without the AutoMap attribute.  The AutoMap attribute allows us to keep some of this code out of the controller which can be quite nice.  One advantage to not using it is that the code inside the controller class is more obvious and explicit.  Additionally, our builder and mapper classes might need to build the objects from multiple sources and repositories. Internally in our mapper classes, you can still make great use of tools like AutoMapper.

In many complex real-world cases, some variation of pattern #3 is the best choice as it affords the most flexibility to the developer.

 

Considerations

How do you determine the best approach to take?  Here are some considerations to keep in mind:

Code Re-use – Certainly patterns #1 and #2 lend themselves best to code re-use as you are binding your views directly to your domain model objects. This leads to increased code brevity as mapping layers are not required. However, if your view concerns differ from your domain model (which they often will) options #1 and #2 begin to break down.

Impedance mismatch – Often there is an impedance mismatch between your domain model and the concerns of your view.  In these cases, option #3 gives the most flexibility.

Mapping Layer – If custom view entities are used as in option #3, you must ensure you establish a pattern for a mapping layer. Although this means more code that must be written, it gives the most flexibility and there are libraries available such as AutoMapper that make this easier to implement.

Validation – Although there are many ways to perform validation, one of the most common is to use libraries like Data Annotations. Although typical validations (e.g., required fields, etc.) will probably be the same between your domain models and your views, not all validation will always match.  Additionally, you may not always be in control of your domain models (e.g., in some enterprises the domain models are exposed via services that UI developers simply consume).  So there is a limit to how you can associate validations with those classes.  Yes, you can use a separate “meta data” class to designate validations but this duplicates some code similar to how a view model entity from option #3 would anyway. Therefore, option #3 gives you the absolute most control over UI validation.

 

Conclusion

The following has been a summary of several of the patterns that have emerged in dealing with view models. Although these have all been in the context of ASP.NET MVC, the problem with how best to deal with view models is also an issue with other frameworks like web forms as well. If you are able to bind directly to domain model in simple cases, that is the simplest and easiest solution. However, as your complexity grows, having distinct view models gives you the most overall flexibility.

[MVC]12个asp.net MVC最佳实践

mikel阅读(471)

      上一周我我在罗马进行了两场对于开发完成不久的http://www.dotnetromacesta.org/ASP.NET MVC的演讲。而其中一场演讲内容是关于我对于ASP.NET MVC最佳实践的看法.因为这场演讲是在意大利进行的,为了大家能更好的阅读,我将演讲所用的ppt翻译成英文。

     

关于Controller的最佳实践

1-删除AccountController

    让Demo代码在你的程序中是一个非常不好的做法。请永远不要使用AccountController.

2-隔离外部网络和Controller

     如果依赖HttpContext,数据访问类,配置,日志等,则会让程序难以测试,修改或者进一步开发。

3-使用一个IOC容器

    使达到第二条最佳实践更加容易,使用IOC容器管理所有外部依赖我使用 Ninject v2,这种IOC容器有很多,如果需要的话,你甚至可以自己实现一个。

4-和“神奇的strings”说不

    永远不要使用ViewData[“key”],而要为每一个视图创建一个ViewModel,从而使用强类型的ViewPage<ViewModel>.

    神奇的Strings是很邪恶的,因为你可能由于错误的拼写而导致视图出错,而强类型的Model不仅可以有智能感知,而且错误是在编译时获取而不是在运行时。

5-创建你自己的“个人惯例”

    使用ASP.NET MVC作为你个人(或者公司)的参考构架的基础,你还可以使Controller和View继承于你自己的基类而不是默认的基类来让你的惯例更加透彻。

6-注意Verbs

    就算不使用最合适的HTTP Verb,最要也要采用PRG模式,(Post-Redirect-Get):使用Get来显示数据,使用Post来修改数据。

  

关于Model的最佳实践

7–DomainModel != ViewModel

     DomainModel 代表着相应的域,但ViewModel却是为View的需要而创建。这两者之间或许(一般情况下都)是不同的,此外DomainModel是数据加上行为 的组合体,是由复杂的变量类型组成的并且具有层次。而ViewModel只是由一些String等简单变量类型组成。如果想移除冗余并且容易导致出错的 orM代码,可以使用AutoMapper.如果想要了解更多,我推荐阅读:ASP.NET MVC View Model Patterns.

8-为“共享”的数据使用ActionFilter

     这是我自己的解决方案,或许需要在未来发帖继续探讨。通常情况下,你都不希望你的Controller获取的数据在几个不同的View之间共享,我的方法则是使用ActionFilter来获取在几个不同View之间共享的数据,然后用合适的View来显示。

关于View的最佳实践

9-不要使用CodeBehind模式

      永远不要。

10-尽可能的写HTML代码

      我认为Web开发人员必须的习惯于写HTML(或者CSS和JavaScript).所以最好少用仅仅用来隐藏HTML代码的HTMLHelper(比如HTML.Submit或者HTML.Button).这也是我会在未来的帖子里讨论的。

11-如果有if语句,使用HTMLHelper

      View必须是哑巴(Controller是瘦子而Model是胖子),如果你发现自己在使用if语句,那就写一个HTMLHelper来隐藏选择条件语句.

12-仔细的选择你的View引擎

     默认的引擎室WebFormViewEngine,IMHO并不是最好的引擎,我更倾向于选择Spark ViewEngine,因为对于我来说这个引擎更适合MVC的View.我喜欢的是“dominates the flow and that code should fit seamlessly”对于每一次循环来说IF语句都会被定义在”HTML标签“中.

DEMO代码和幻灯片的下载

     你可以下载幻灯片或者demo代码

 

 

 

 

原文链接:http://codeclimber.net.nz/archive/2009/10/27/12-asp.net-mvc-best-practices.aspx

[MVC]蛙蛙推荐:ASP.NET MVC学习笔记

mikel阅读(510)

评论:讲述了开发中的常常用到的知识

转载:http://www.cnblogs.com/onlytiancai/archive/2009/10/31/asp_net_mvc_study.html

ASP.NET MVC 1.0发布有段儿时间了,刚发布不久的时候试用了一下,做了一个简单的BBS,现在总结一些经验和小技巧和大家分享。网上关于ASP.NET MVC的系列教程有好几个,所以就不从头开始介绍了,结尾处给大家推荐了几个链接,需要的话可以从头系统的看看。

1ASP.NET MVC介绍及与ASP.NET WebForm的区别

刚开始为了搞清楚ASP.NET MVC到底值不值得用,翻来覆去想了一个多礼拜,看了好多资料和评论,最后决定还是值得一用。MVC不是一个简单的设计模式,更像一种架构模式,或者一种思想,刚开始一听MVC想到的就是模板引擎,NVelocityStringTempleate等,但感觉如果只是为了用模板这种独立的前台设计方式,没必要用ASP.NET MVC,大多数情况用Repeaterk控件和自定义控件儿就能做到,而且ASPX页面上本来就可以写C#代码,一些比较复杂的界面表现逻辑用普通的WebForm也能实现,其实ASP.NET MVCVIEW部分默认用的还是aspx的解析器。ASP.NET MVCView部分让你写一些大型的,布局复杂的网站更方便,更底层,更直接,很受对css,js很熟悉的开发者的欢迎。

当你理解了MVC的思想后,会发现ASP.NET MVC的好处真正在于ControllerAction,你写一段代码能很明确的知道是在处理什么请求,毕竟web程序处理的是一个一个的http请求,不像windows桌面程序,基于事件驱动更直观。ASP.NET MVCController让你写一些web api或者rest风格的接口很方便(以前可能要用HttpHandler来做),这些Controller只负责提供数据(具体的ActionResult类,如JsonResult,JavaScriptResult等)给使用者,比如一个Ajax调用,或者View层。

至于Model层,我看网上大多数人是用LINQ TO SQL实现的,毕竟使用起来很简单,设计好表,用LINQ 设计器往vs.net里一拖就能用了。而且本身就是强类型的,再在自动生成的代码上加一些分部方法,就可以实现数据的有效性验证等。还有就是对LINQ做的Model进行数据持久化和查询的时候更方便,直接用DbContext一个类,增删改查全能搞定。

有得就有舍,ASP.NET MVC虽然提供了先进的思想和一些便利,但ASP.NET以前的一些东西不能用了,比如以前自己写的一些服务器控件儿不能用了,WebPart,皮肤,各种数据绑定控件等都不能用了,但Master页还能用,Asp.net Ajax control toolkit(服务端)也不能用了,但asp.net ajax library(客户端js库)还能继续使用,基于页面和目录的授权不能用了(因为现在没页面,只有view了),但MemberShipForms身份验证还是支持的。标准WebForm的生命周期变了,好些事件没了,现在你可以写一些拦截器(Action拦截器、Result拦截器和Exception拦截器)来影响请求的处理过程,还有一些区别,总之失去的东西,都有变通的方法能找吧回来。

2linq to SQL如何获取插入语句产生的标识列的值?

其实很简单,把对象插入数据库后,直接取值就行了,如下BBSPost是一个实体类,其中PostID在数据库里是自增列。

var db = new BBSDbContext(connstr);

BBSPost post = new BBSPost()

post.PostUser = User.Identity.Name;

post.PostTime = DateTime.Now;

db.BBSPosts.InsertOnSubmit(post);

db.SubmitChanges();

int postid = post.PostID; //这里就能取到标识列的值。

 

3ASP.NET MVC里在请求提交后如何后维持滚动条位置?

WebForm里再简单不过了,在web.config里配置MaintainScrollPositionOnPostBack=true就搞定了,但在MVC里就不行了。我们知道了原理后,可以自己实现,其实就是在提交表单或者滚动条滚动的事件里捕获当前滚动条的位置,把数值放在一个隐藏域里,提交给服务端,服务端应答后,从隐藏域里取出滚动条的位置,用js操纵滚动条滚动到上次的位置。

我们先在View里写一个隐藏域,如下

 <%= Html.Hidden("scroll", ViewData["scrool"])%>

然后在处理客户端请求的action里给ViewData里存储一下提交上来的值(从FormCollection里取)。

public ActionResult reply(BBSPost post, FormCollection coll) {

         …

         ViewData["scroll"] = coll["scroll"];

         …

         return View("show_post",posts);

}

这样页面提交后隐藏域里就会保存着提交前滚动条的位置,然后我们在用JQuery写一些逻辑实现最终的效果。

<script type="text/JavaScript">

    $(function() {

        $(document).scroll(function() {

        //在滚动条滚动的时候更新隐藏域里滚动条的位置值,经测试不支持IE8,汗

            $("#scroll").val(document.documentElement.scrollTop);

        });

        $("form").submit(function() {

        //在表单提交的时候更新隐藏域里滚动条的位置值

            $("#scroll").val(document.documentElement.scrollTop);

            return true;

        });

        //document.load事件里取出隐藏域的值,并设置滚动条的位置

        document.documentElement.scrollTop = $("#scroll").val();           

    });

</script>

4、验证用户输入

数据有效性的验证基本上哪个程序都躲不了,LINQ ASP.NET MVC的配合,让数据验证的实现也很方便。

LINQ TO SQL设计器自动生成的类是一个分部类,就是半块儿的类,你可以写一个分步类,在自动生成的类上加一些扩展的方法,如下我们在LINQ实体类BBSPost上加了一个GetRuleViolations方法,一个IsValid属性,其中GetRuleViolations方法验证给实体类赋的值的有效性,用yield关键字返回一个枚举器,这里可以写你自己的数据有效性验证逻辑。

IsValid属性内部调用GetRuleViolations方法,如果返回的枚举器的Count不是0的话,表示数据有效性验证不通过。

另外为了方式LINQ TO SQL往数据库里写入无效数据,我们给OnValidate分布方法加了两行代码,在数据有效性验证不通过的情况下写数据库之前抛出异常。

public partial class BBSPost {

    public bool IsValid {

        get { return (GetRuleViolations().Count() == 0); }

    }

    public IEnumerable<RuleViolation> GetRuleViolations() {

        if (String.IsNullOrEmpty(Title))

            yield return new RuleViolation("标题必须输入", "Title");

        if (String.IsNullOrEmpty(Content))

            yield return new RuleViolation("内容必须输入", "Content");

        yield break;

    }

    partial void OnValidate(ChangeAction action) {

        if (!IsValid)

            throw new ApplicationException("Rule violations prevent saving");

    }

}

RuleViolation是一个辅助类,很简单。

public class RuleViolation {

    public string ErrorMessage { get; private set; }

    public string PropertyName { get; private set; }

    public RuleViolation(string errorMessage) {

        ErrorMessage = errorMessage;

    }

    public RuleViolation(string errorMessage, string propertyName) {

        ErrorMessage = errorMessage;

        PropertyName = propertyName;

    }

}

在写action的时候,捕获SubmitChanges操作的异常,然后给ModelState里添加自定义验证逻辑的异常,ModelState会把添加进去的异常传递给View层,供View层使用。

try {

    var db = new BBSDbContext(GlobalHelper.Conn);

    post.PostUser = User.Identity.Name;

    //其它赋值操作

    db.BBSPosts.InsertOnSubmit(post);

    db.SubmitChanges();

    ModelState.Clear();

}

catch (Exception ex) {

    ModelState.AddModelErrors(post.GetRuleViolations());

    ModelState.AddModelError("exception", ex);

}

默认的ModelState没有AddModelErrors方法,只有AddModelError方法,我们是后来给他加了一个扩展方法,如下

public static class ModelStateHelpers {

    public static void AddModelErrors(this ModelStateDictionary modelState, IEnumerable<RuleViolation> errors) {

        foreach (RuleViolation issue in errors) {

            modelState.AddModelError(issue.PropertyName, issue.ErrorMessage);

        }

    }

}

View层使用了Html.ValidationMessage方法在合适的位置输出错误描述,如果View呈现的时候ModelState里有错误的话,会自动显示相应的错误描述,代码示例如下。

<p>

    <label for="Title">

        标题:</label>

    <%= Html.TextBox("Title", null, new { style = "width:700px;" })%>

    <%= Html.ValidationMessage("Title") %>

</p>

<p>

    <label for="Content">

        内容:</label>

    <%= Html.TextArea("Content", null, new { style = "width:700px;height:100px;" })%>

    <%= Html.ValidationMessage("Content")%>

</p>

5LINGQ TO SQL的分页

SQLServer 2005有很强悍的分页函数,LINQ TO SQL对其有很好的支持,IQueryable<T>SkipTake方法最终就生成分页的SQL,先写如下的一个帮助类(取自NerdDinner),这个类的属性很简单,见名知意,就不介绍了。

public class PaginatedList<T> : List<T> {

    public int PageIndex  { get; private set; }

    public int PageSize   { get; private set; }

    public int TotalCount { get; private set; }

    public int TotalPages { get; private set; }

    public PaginatedList(IQueryable<T> source, int pageIndex, int pageSize) {

        PageIndex = pageIndex;

        PageSize = pageSize;

        TotalCount = source.Count();

        TotalPages = (int) Math.Ceiling(TotalCount / (double)PageSize);

        this.AddRange(source.Skip(PageIndex * PageSize).Take(PageSize)); //这句会停止延迟加载,把数据加载到内存里

    }

    public bool HasPreviousPage {

        get {

            return (PageIndex > 0);

        }

    }

    public bool HasNextPage {

        get {

            return (PageIndex+1 < TotalPages);

        }

    }

}

使用起来很简单,LINQ TO SQL得到一个IQueryable后,再用其New一个PaginatedList就表示一个已分页的数据集了

var posts = from post in db.BBSPosts

    where post.CategoryID == id && post.ParentID == 0

    orderby post.PostID descending

    select post;

const int pageSize = 10;

var pagePosts = new PaginatedList<BBSPost>(posts, page ?? 0, pageSize);

return View(pagePosts);

posts是用linq to sql生成的一个IQueryable<BBSPost>对象,这时候SQL语句并没有执行,会延迟执行,再new一个PaginatedList<BBSPost>的时候会对其生成的SQL语句进行修改,最后把pagePosts传递给view层用就行了,View层我们使用了强类型的View,如下

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"

         Inherits="System.Web.Mvc.ViewPage<SimpleBBS.Helpers.PaginatedList<SimpleBBS.Models.BBSPost>>" %>

页面上要显示上一页,下一页的链接,写起来也很简单

<div class="pagination">

    <% if (Model.HasPreviousPage) { %>

    <%= Html.RouteLink("上一页",

                                           "Default",

                           new { page=(Model.PageIndex-1) }) %>

    <% } %>

    <% if (Model.HasNextPage) { %>

    <%= Html.RouteLink("下一页",

                                           "Default",

                           new { page = (Model.PageIndex + 1) })%>

    <% } %>

</div>      

 

6、查看LINQ TO SQL生成的SQL语句?

有人怀疑LINQ TO SQL的性能问题,认为它生成的语句不靠谱,其实它生成的语句都是参数化查询,一般的基于主键或者索引列的查询及大多数更新操作性能应该不会比手写SQL差,如果还是不放心的话,可以把LINQ TO SQL生成的SQL打印出来,以避免性能查的语句产生。

如下代码

var db = new BBSDbContext(conn);

var posts = from post in db.BBSPosts

        where post.CategoryID == 1 && post.ParentID == 0

        orderby post.PostID descending

        select new {post.PostID, post.Title, post.Content};

db.Log = Response.Output; //跟踪自动生成的SQL语句

rpt1.DataSource = posts;

rpt1.DataBind(); //只有真正执行使用数据的语句时,SQL查询才会执行,在这之前语句只是语句,自动延迟执行的。

会在页面上看到LINQ TO SQL生成的SQL语句

Select [t0].[PostID], [t0].[Title], [t0].[Content] FROM [dbo].[bbs_Post] AS [t0] Where ([t0].[CategoryID] = @p0) AND ([t0].[ParentID] = @p1) orDER BY [t0].[PostID] DESC — @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [1] — @p1: Input Int (Size = 0; Prec = 0; Scale = 0) [0] — Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.30729.1

如果改成如下分页方式

var db = new BBSDbContext(conn);

var posts = from post in db.BBSPosts

          where post.CategoryID == 1 && post.ParentID == 0

          orderby post.PostID descending

          select post;

db.Log = Response.Output;

rpt1.DataSource = posts.Skip(1 * 5).Take(5);

rpt1.DataBind();

会输出如下SQL

Select [t1].[CategoryID], [t1].[PostID], [t1].[ParentID], [t1].[Title], [t1].[Content], [t1].[PostUser], [t1].[PostTime] FROM ( Select ROW_NUMBER() OVER (ORDER BY [t0].[PostID] DESC) AS [ROW_NUMBER], [t0].[CategoryID], [t0].[PostID], [t0].[ParentID], [t0].[Title], [t0].[Content], [t0].[PostUser], [t0].[PostTime] FROM [dbo].[bbs_Post] AS [t0] Where ([t0].[CategoryID] = @p0) AND ([t0].[ParentID] = @p1) ) AS [t1] Where [t1].[ROW_NUMBER] BETWEEN @p2 + 1 AND @p2 + @p3 orDER BY [t1].[ROW_NUMBER] — @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [1] — @p1: Input Int (Size = 0; Prec = 0; Scale = 0) [0] — @p2: Input Int (Size = 0; Prec = 0; Scale = 0) [5] — @p3: Input Int (Size = 0; Prec = 0; Scale = 0) [5] — Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.30729.1

可以看到这些查询用的都是参数化查询,不是拼SQL,而且还用了ROW_NUMBER函数,LINQ TO SQL还是比较了解SQLServer的。

7、设置某个Action需要身份认证?

因为基于页面的授权不能使用了,我们只好对某个Action进行授权,比如要回复帖子的话需要进行登录,那么就在replyaction上加上需要身份验证的属性修饰,如下

[AcceptVerbs(HttpVerbs.Post), Authorize]

?public ActionResult reply(BBSPost post, FormCollection coll) {

这种方式是以AOP注入方式实现的,更多的拦截器示例,或者想写自己的拦截器可以google些资料看看。

8、如何把用户提交的表单数据转成强类型。

我们都知道网页上提交的数据包括Form里和QueryString,在服务端取出来都是string类型的,在asp时代,我们需要一个一个的处理参数,在ASP.NET MVC里就很方便了,比如你有一个BBSPost类,有TitleContentCategoryId 3个属性,而表单上有两个文本框TitleContent,地址栏参数里有一个CategoryId,你可以直接在action里取到一个BBSPost类,而且属性都给你填充好了,不用你取出一个一个的stringnew一个BBSPost类,再转类型赋值等一系列操作了,如下

public ActionResult reply(BBSPost post, FormCollection coll) {}

第一个参数会自动填充成强类型,第二个参数可以取出原始的表单提交的数据。如果你想了解更多的表单数据和强类型数据的绑定,细节,可以查查DefaultModelBinder是如何工作的。

9、给HTMLHelper加扩展方法。

ASP.NET MVC里的一个最佳实践就是给HTMLHelper加一些常用的扩展方法以供View层方便使用,不要到处写帮助类,比如在显示帖子的时候要格式化帖子成HTML格式,我们写了如下的扩展方法

public static class HtmlHelperExtension {

    public static string Text2Html(this HtmlHelper helper, string input) {

        input = input.Replace(" ", "&nbsp;");

        input = input.Replace("\r\n", "<br />");

        input = input.Replace("\t", "&nbsp;&nbsp;&nbsp;");

        return input;

    }

}

view上先引用扩展方法所在的命名空间

<%@ Import Namespace="SimpleBBS.Helpers" %>

然后扩展方法就能使用了,如下

<%= Html.Text2Html(Html.Encode(item.Content)) %>

 

10、如何定位脚本和CSS的位置

如果我们目录级别特别多,把脚本,样式表等放在一个固定的目录后,在特定的子目录访问这些资源路径可能不一致,在WebForm的时候只有服务端控件才能使用~语法,无论是部署在站点根目录还是虚拟目录,~都能表示应用的根目录,在ASP.NET MVC里我们可以用Url.Content来使用~,如下

<script src="<%=Url.Content("~/Scripts/JQuery-1.3.2.min.js")%>" type="text/javascript"></script>  

参考链接

一步一步学习ASP.NET MVC 1.0创建NerdDinner 范例程序

http://forum.entlib.com/Default.aspx?g=posts&t=476

ASP.NET MVC 1.0学习笔记

http://blog.csdn.net/hzzasdf/archive/2009/05/16/4191439.aspx

从零开始学习ASP.NET MVC 1.0

http://www.cnblogs.com/zhangziqiu/archive/2009/02/27/ASPNET-MVC-1.html

ASP.NET MVC案例教程》索引贴

http://www.cnblogs.com/leoo2sk/archive/2008/11/02/1325084.html

Repository模式

http://www.cnblogs.com/carysun/archive/2009/03/20/Repository.html

LINQ to SQL语句入门篇

http://kb.cnblogs.com/zt/linq/list/42463/

一步一步学LINQ TO SQL

http://kb.cnblogs.com/zt/linq/list/42687/

[原创]取得title=""的正则表达式

mikel阅读(517)

title=(.*?")(.*?")
说明:
title=是html代码中的关键字可以扩展一下取得href=,src=等
(.*?"):取得=后面的所有字符+双引号起始字符
(.*?"):取得“起始符+所有内容+"第一个出现的结束符之间的内容
实例:
 <img id="ctl00_imgCode" title="上海申花  节能热水产品专家" class="currentIcon" onload="fixPng(this)" src="http://l.yimg.com/a/i/us/nws/weather/gr/14s.png" height="34" width="61" border="0" /><span id="ctl00_ltlText">小雪</span>

[MVC]Validating with a Service Layer

mikel阅读(500)

转载:http://www.ASP.NET/learn/mvc/tutorial-38-cs.aspx

Validating with a Service Layer (C#)

The goal of this tutorial is to describe one method of performing validation in an ASP.NET MVC application. In this tutorial, you learn how to move your validation logic out of your controllers and into a separate service layer.

Separating Concerns

When you build an ASP.NET MVC application, you should not place your database logic inside your controller actions. Mixing your database and controller logic makes your application more difficult to maintain over time. The recommendation is that you place all of your database logic in a separate repository layer.

For example, Listing 1 contains a simple repository named the ProductRepository. The product repository contains all of the data access code for the application. The listing also includes the IProductRepository interface that the product repository implements.

Listing 1 — Models\ProductRepository.cs

using System.Collections.Generic;
using System.Linq;
namespace MvcApplication1.Models
{
public class ProductRepository : MvcApplication1.Models.IProductRepository
{
private ProductDBEntities _entities = new ProductDBEntities();
public IEnumerable<Product> ListProducts()
{
return _entities.ProductSet.ToList();
}
public bool CreateProduct(Product productToCreate)
{
try
{
_entities.AddToProductSet(productToCreate);
_entities.SaveChanges();
return true;
}
catch
{
return false;
}
}
}
public interface IProductRepository
{
bool CreateProduct(Product productToCreate);
IEnumerable<Product> ListProducts();
}
}

The controller in Listing 2 uses the repository layer in both its Index() and Create() actions. Notice that this controller does not contain any database logic. Creating a repository layer enables you to maintain a clean separation of concerns. Controllers are responsible for application flow control logic and the repository is responsible for data access logic.

Listing 2 – Controllers\ProductController.cs

using System.Web.Mvc;
using MvcApplication1.Models;
namespace MvcApplication1.Controllers
{
public class ProductController : Controller
{
private IProductRepository _repository;
public ProductController():
this(new ProductRepository()) {}
public ProductController(IProductRepository repository)
{
_repository = repository;
}
public ActionResult Index()
{
return View(_repository.ListProducts());
}
//
// GET: /Product/Create
public ActionResult Create()
{
return View();
}
//
// POST: /Product/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude="Id")] Product productToCreate)
{
_repository.CreateProduct(productToCreate);
return RedirectToAction("Index");
}
}
}

Creating a Service Layer

So, application flow control logic belongs in a controller and data access logic belongs in a repository. In that case, where do you put your validation logic? One option is to place your validation logic in a service layer.

A service layer is an additional layer in an ASP.NET MVC application that mediates communication between a controller and repository layer. The service layer contains business logic. In particular, it contains validation logic.

For example, the product service layer in Listing 3 has a CreateProduct() method. The CreateProduct() method calls the ValidateProduct() method to validate a new product before passing the product to the product repository.

Listing 3 – Models\ProductService.cs

using System.Collections.Generic;
using System.Web.Mvc;
namespace MvcApplication1.Models
{
public class ProductService : MvcApplication1.Models.IProductService
{
private ModelStateDictionary _modelState;
private IProductRepository _repository;
public ProductService(ModelStateDictionary modelState, IProductRepository repository)
{
_modelState = modelState;
_repository = repository;
}
protected bool ValidateProduct(Product productToValidate)
{
if (productToValidate.Name.Trim().Length == 0)
_modelState.AddModelError("Name", "Name is required.");
if (productToValidate.Description.Trim().Length == 0)
_modelState.AddModelError("Description", "Description is required.");
if (productToValidate.UnitsInStock < 0)
_modelState.AddModelError("UnitsInStock", "Units in stock cannot be less than zero.");
return _modelState.IsValid;
}
public IEnumerable<Product> ListProducts()
{
return _repository.ListProducts();
}
public bool CreateProduct(Product productToCreate)
{
// Validation logic
if (!ValidateProduct(productToCreate))
return false;
// Database logic
try
{
_repository.CreateProduct(productToCreate);
}
catch
{
return false;
}
return true;
}
}
public interface IProductService
{
bool CreateProduct(Product productToCreate);
IEnumerable<Product> ListProducts();
}
}

The Product controller has been updated in Listing 4 to use the service layer instead of the repository layer. The controller layer talks to the service layer. The service layer talks to the repository layer. Each layer has a separate responsibility.

Listing 4 – Controllers\ProductController.cs

Listing 4 – Controllers\ProductController.cs
using System.Web.Mvc;
using MvcApplication1.Models;
namespace MvcApplication1.Controllers
{
public class ProductController : Controller
{
private IProductService _service;
public ProductController()
{
_service = new ProductService(this.ModelState, new ProductRepository());
}
public ProductController(IProductService service)
{
_service = service;
}
public ActionResult Index()
{
return View(_service.ListProducts());
}
//
// GET: /Product/Create
public ActionResult Create()
{
return View();
}
//
// POST: /Product/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude = "Id")] Product productToCreate)
{
if (!_service.CreateProduct(productToCreate))
return View();
return RedirectToAction("Index");
}
}
}

Notice that the product service is created in the product controller constructor. When the product service is created, the model state dictionary is passed to the service. The product service uses model state to pass validation error messages back to the controller.

Decoupling the Service Layer

We have failed to isolate the controller and service layers in one respect. The controller and service layers communicate through model state. In other words, the service layer has a dependency on a particular feature of the ASP.NET MVC framework.

We want to isolate the service layer from our controller layer as much as possible. In theory, we should be able to use the service layer with any type of application and not only an ASP.NET MVC application. For example, in the future, we might want to build a WPF front-end for our application. We should find a way to remove the dependency on ASP.NET MVC model state from our service layer.

In Listing 5, the service layer has been updated so that it no longer uses model state. Instead, it uses any class that implements the IValidationDictionary interface.

Listing 5 – Models\ProductService.cs (decoupled)

using System.Collections.Generic;
namespace MvcApplication1.Models
{
public class ProductService : IProductService
{
private IValidationDictionary _validatonDictionary;
private IProductRepository _repository;
public ProductService(IValidationDictionary validationDictionary, IProductRepository repository)
{
_validatonDictionary = validationDictionary;
_repository = repository;
}
protected bool ValidateProduct(Product productToValidate)
{
if (productToValidate.Name.Trim().Length == 0)
_validatonDictionary.AddError("Name", "Name is required.");
if (productToValidate.Description.Trim().Length == 0)
_validatonDictionary.AddError("Description", "Description is required.");
if (productToValidate.UnitsInStock < 0)
_validatonDictionary.AddError("UnitsInStock", "Units in stock cannot be less than zero.");
return _validatonDictionary.IsValid;
}
public IEnumerable<Product> ListProducts()
{
return _repository.ListProducts();
}
public bool CreateProduct(Product productToCreate)
{
// Validation logic
if (!ValidateProduct(productToCreate))
return false;
// Database logic
try
{
_repository.CreateProduct(productToCreate);
}
catch
{
return false;
}
return true;
}
}
public interface IProductService
{
bool CreateProduct(Product productToCreate);
System.Collections.Generic.IEnumerable<Product> ListProducts();
}
}

The IValidationDictionary interface is defined in Listing 6. This simple interface has a single method and a single property.

Listing 6 – Models\IValidationDictionary.cs

namespace MvcApplication1.Models
{
public interface IValidationDictionary
{
void AddError(string key, string errorMessage);
bool IsValid { get; }
}
}

The class in Listing 7, named the ModelStateWrapper class, implements the IValidationDictionary interface. You can instantiate the ModelStateWrapper class by passing a model state dictionary to the constructor.

Listing 7 – Models\ModelStateWrapper.cs

using System.Web.Mvc;
namespace MvcApplication1.Models
{
public class ModelStateWrapper : IValidationDictionary
{
private ModelStateDictionary _modelState;
public ModelStateWrapper(ModelStateDictionary modelState)
{
_modelState = modelState;
}
#region IValidationDictionary Members
public void AddError(string key, string errorMessage)
{
_modelState.AddModelError(key, errorMessage);
}
public bool IsValid
{
get { return _modelState.IsValid; }
}
#endregion
}
}

Finally, the updated controller in Listing 8 uses the ModelStateWrapper when creating the service layer in its constructor.

Listing 8 – Controllers\ProductController.cs

using System.Web.Mvc;
using MvcApplication1.Models;
namespace MvcApplication1.Controllers
{
public class ProductController : Controller
{
private IProductService _service;
public ProductController()
{
_service = new ProductService(new ModelStateWrapper(this.ModelState), new ProductRepository());
}
public ProductController(IProductService service)
{
_service = service;
}
public ActionResult Index()
{
return View(_service.ListProducts());
}
//
// GET: /Product/Create
public ActionResult Create()
{
return View();
}
//
// POST: /Product/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude = "Id")] Product productToCreate)
{
if (!_service.CreateProduct(productToCreate))
return View();
return RedirectToAction("Index");
}
}
}

Using the IValidationDictionary interface and the ModelStateWrapper class enables us to completely isolate our service layer from our controller layer. The service layer is no longer dependent on model state. You can pass any class that implements the IValidationDictionary interface to the service layer. For example, a WPF application might implement the IValidationDictionary interface with a simple collection class.

Summary

The goal of this tutorial was to discuss one approach to performing validation in an ASP.NET MVC application. In this tutorial, you learned how to move all of your validation logic out of your controllers and into a separate service layer. You also learned how to isolate your service layer from your controller layer by creating a ModelStateWrapper class.

[MVC]MVC应用程序执行过程解析

mikel阅读(483)

转载:http://www.ASP.NET/learn/mvc/tutorial-22-cs.aspx

Understanding the MVC Application Execution Process (C#)

Requests to an ASP.NET MVC-based Web application first pass through the UrlRoutingModule object, which is an HTTP module. This module parses the request and performs route selection. The UrlRoutingModule object selects the first route object that matches the current request. (A route object is a class that implements RouteBase, and is typically an instance of the Route class.) If no routes match, the UrlRoutingModule object does nothing and lets the request fall back to the regular ASP.NET or IIS request processing.
From the selected Route object, the UrlRoutingModule object obtains the IRouteHandler object that is associated with the Route object. Typically, in an MVC application, this will be an instance of MvcRouteHandler. The IRouteHandler instance creates an IHttpHandler object and passes it the IHttpContext object. By default, the IHttpHandler instance for MVC is the MvcHandler object. The MvcHandler object then selects the controller that will ultimately handle the request.

Note:
When an ASP.NET MVC Web application runs in IIS 7.0, no file name extension is required for MVC projects. However, in IIS 6.0, the handler requires that you map the .mvc file name extension to the ASP.NET ISAPI DLL.

The module and handler are the entry points to the ASP.NET MVC framework. They perform the following actions:

  • Select the appropriate controller in an MVC Web application.
  • Obtain a specific controller instance.
  • Call the controller's Execute method.

The following table lists the stages of execution for an MVC Web project.

Stage Details
Receive first request for the application In the Global.asax file, Route objects are added to the RouteTable object.
Perform routing The UrlRoutingModule module uses the first matching Route object in the RouteTable collection to create the RouteData object, which it then uses to create a RequestContext (IHttpContext) object.
Create MVC request handler The MvcRouteHandler object creates an instance of the MvcHandler class and passes it the RequestContext instance.
Create controller The MvcHandler object uses the RequestContext instance to identify the IControllerFactory object (typically an instance of the DefaultControllerFactory class) to create the controller instance with.
Execute controller The MvcHandler instance calls the controller's Execute method.
Invoke action Most controllers inherit from the Controller base class. For controllers that do so, the ControllerActionInvoker object that is associated with the controller determines which action method of the controller class to call, and then calls that method.
Execute result A typical action method might receive user input, prepare the appropriate response data, and then execute the result by returning a result type. The built-in result types that can be executed include the following: ViewResult (which renders a view and is the most-often used result type), RedirectToRouteResult, RedirectResult, ContentResult, JsonResult, and EmptyResult.

[MVC]Creating a Route Constraint

mikel阅读(498)

转载:http://www.ASP.NET/learn/mvc/tutorial-24-cs.aspx

Creating a Route Constraint (C#)

You use route constraints to restrict the browser requests that match a particular route. You can use a regular expression to specify a route constraint.

For example, imagine that you have defined the route in Listing 1 in your Global.asax file.

Listing 1 – Global.asax.cs

routes.MapRoute( "Product", "Product/{productId}", new {controller="Product", action="Details"} );

Listing 1 contains a route named Product. You can use the Product route to map browser requests to the ProductController contained in Listing 2.

Listing 2 – Controllers\ProductController.cs

using System.Web.Mvc; namespace MvcApplication1.Controllers { public class ProductController : Controller { public ActionResult Details(int productId) { return View(); } } }

Notice that the Details() action exposed by the Product controller accepts a single parameter named productId. This parameter is an integer parameter.

The route defined in Listing 1 will match any of the following URLs:

  • /Product/23
  • /Product/7

Unfortunately, the route will also match the following URLs:

  • /Product/blah
  • /Product/apple

Because the Details() action expects an integer parameter, making a request that contains something other than an integer value will cause an error. For example, if you type the URL /Product/apple into your browser then you will get the error page in Figure 1.

Figure 01: Seeing a page explode (Click to view full-size image)

What you really want to do is only match URLs that contain a proper integer productId. You can use a constraint when defining a route to restrict the URLs that match the route. The modified Product route in Listing 3 contains a regular expression constraint that only matches integers.

Listing 3 – Global.asax.cs

routes.MapRoute( "Product", "Product/{productId}", new {controller="Product", action="Details"}, new {productId = @"\d+" } );

The regular expression \d+ matches one or more integers. This constraint causes the Product route to match the following URLs:

  • /Product/3
  • /Product/8999

But not the following URLs:

  • /Product/apple
  • /Product

These browser requests will be handled by another route or, if there are no matching routes, a The resource could not be found error will be returned.

[MVC]Adding Dynamic Content to a Cached Page

mikel阅读(539)

转载:http://www.ASP.NET/learn/mvc/tutorial-19-cs.aspx

Adding Dynamic Content to a Cached Page (C#)

By taking advantage of output caching, you can dramatically improve the performance of an ASP.NET MVC application. Instead of regenerating a page each and every time the page is requested, the page can be generated once and cached in memory for multiple users.

But there is a problem. What if you need to display dynamic content in the page? For example, imagine that you want to display a banner advertisement in the page. You don’t want the banner advertisement to be cached so that every user sees the very same advertisement. You wouldn’t make any money that way!

Fortunately, there is an easy solution. You can take advantage of a feature of the ASP.NET framework called post-cache substitution. Post-cache substitution enables you to substitute dynamic content in a page that has been cached in memory.

Normally, when you output cache a page by using the [OutputCache] attribute, the page is cached on both the server and the client (the web browser). When you use post-cache substitution, a page is cached only on the server.

Using Post-Cache Substitution

Using post-cache substitution requires two steps. First, you need to define a method that returns a string that represents the dynamic content that you want to display in the cached page. Next, you call the HttpResponse.WriteSubstitution() method to inject the dynamic content into the page.

Imagine, for example, that you want to randomly display different news items in a cached page. The class in Listing 1 exposes a single method, named RenderNews(), that randomly returns one news item from a list of three news items.

Listing 1 – Models\News.cs

using System;
using System.Collections.Generic;
using System.Web;
namespace MvcApplication1.Models
{
public class News
{
public static string RenderNews(HttpContext context)
{
var news = new List<string>
{
"Gas prices go up!",
"Life discovered on Mars!",
"Moon disappears!"
};
var rnd = new Random();
return news[rnd.Next(news.Count)];
}
}
}

To take advantage of post-cache substitution, you call the HttpResponse.WriteSubstitution() method. The WriteSubstitution() method sets up the code to replace a region of the cached page with dynamic content. The WriteSubstitution() method is used to display the random news item in the view in Listing 2.

Listing 2 – Views\Home\Index.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="MvcApplication1.Views.Home.Index" %>
<%@ Import Namespace="MvcApplication1.Models" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Index</title>
</head>
<body>
<div>
<% Response.WriteSubstitution(News.RenderNews); %>
<hr />
The content of this page is output cached.
<%= DateTime.Now %>
</div>
</body>
</html>

The RenderNews method is passed to the WriteSubstitution() method. Notice that the RenderNews method is not called (there are no parentheses). Instead a reference to the method is passed to WriteSubstitution().

The Index view is cached. The view is returned by the controller in Listing 3. Notice that the Index() action is decorated with an [OutputCache] attribute that causes the Index view to be cached for 60 seconds.

Listing 3 – Controllers\HomeController.cs

using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
[HandleError]
public class HomeController : Controller
{
[OutputCache(Duration=60, VaryByParam="none")]
public ActionResult Index()
{
return View();
}
}
}

Even though the Index view is cached, different random news items are displayed when you request the Index page. When you request the Index page, the time displayed by the page does not change for 60 seconds (see Figure 1). The fact that the time does not change proves that the page is cached. However, the content injected by the WriteSubstitution() method – the random news item – changes with each request .

Figure 1 – Injecting dynamic news items in a cached page

clip_image002

Using Post-Cache Substitution in Helper Methods

An easier way to take advantage of post-cache substitution is to encapsulate the call to the WriteSubstitution() method within a custom helper method. This approach is illustrated by the helper method in Listing 4.

Listing 4 – AdHelper.cs

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Mvc;
namespace MvcApplication1.Helpers
{
public static class AdHelper
{
public static void RenderBanner(this HtmlHelper helper)
{
var context = helper.ViewContext.HttpContext;
context.Response.WriteSubstitution(RenderBannerInternal);
}
private static string RenderBannerInternal(HttpContext context)
{
var ads = new List<string>
{
"/ads/banner1.gif",
"/ads/banner2.gif",
"/ads/banner3.gif"
};
var rnd = new Random();
var ad = ads[rnd.Next(ads.Count)];
return String.Format("<img src='{0}' />", ad);
}
}
}

Listing 4 contains a static class that exposes two methods: RenderBanner() and RenderBannerInternal(). The RenderBanner() method represents the actual helper method. This method extends the standard ASP.NET MVC HtmlHelper class so that you can call Html.RenderBanner() in a view just like any other helper method.

The RenderBanner() method calls the HttpResponse.WriteSubstitution() method passing the RenderBannerInternal() method to the WriteSubsitution() method.

The RenderBannerInternal() method is a private method. This method won’t be exposed as a helper method. The RenderBannerInternal() method randomly returns one banner advertisement image from a list of three banner advertisement images.

The modified Index view in Listing 5 illustrates how you can use the RenderBanner() helper method. Notice that an additional <%@ Import %> directive is included at the top of the view to import the MvcApplication1.Helpers namespace. If you neglect to import this namespace, then the RenderBanner() method won’t appear as a method on the Html property.

Listing 5 – Views\Home\Index.aspx (with RenderBanner() method)

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="MvcApplication1.Views.Home.Index" %>
<%@ Import Namespace="MvcApplication1.Models" %>
<%@ Import Namespace="MvcApplication1.Helpers" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Index</title>
</head>
<body>
<div>
<% Response.WriteSubstitution(News.RenderNews); %>
<hr />
<% Html.RenderBanner(); %>
<hr />
The content of this page is output cached.
<%= DateTime.Now %>
</div>
</body>
</html>

When you request the page rendered by the view in Listing 5, a different banner advertisement is displayed with each request (see Figure 2). The page is cached, but the banner advertisement is injected dynamically by the RenderBanner() helper method.

Figure 2 – The Index view displaying a random banner advertisement

clip_image004

Summary

This tutorial explained how you can dynamically update content in a cached page. You learned how to use the HttpResponse.WriteSubstitution() method to enable dynamic content to be injected in a cached page. You also learned how to encapsulate the call to the WriteSubstitution() method within an HTML helper method.

Take advantage of caching whenever possible – it can have a dramatic impact on the performance of your web applications. As explained in this tutorial, you can take advantage of caching even when you need to display dynamic content in your pages.






Featured Item




ASP.NET Hosting - $4.95 a Month!

Visual C# Tutorials

(Switch to Visual Basic tutorials)