[转载]巧用ASP.net MVC3里的HandleErrorAttribute

mikel阅读(931)

[转载]大叔手记(6):巧用ASP.net MVC3里的HandleErrorAttribute – 汤姆大叔 – 博客园.

前言

一直在给Team的人强调“Good programming is good Error Handling”,没人喜欢YSOD(Yellow Screen of Death)。我每次看到黄页的时候都是心惊肉跳的,尤其是在给客户演示的时候,所以在任何时候,如果出现黄页是由于你开发的代码导致的话,对不起,我会 给你的绩效打很低的分。

当然,有些情况的黄页,在某些特殊的情况,我们可能真的无法预知,但我们起码得一些技巧让终端用户看不到这个YSOD页面。

方案

幸运的是,在MVC3里有现成的功能支持让我们可以做到这一点,它就是HandleErrorAttribte类,有2种方式可以使用它,一是在类或者方法上直接使用HandleError属性来定义:

// 在这里声明
[HandleError]
public class HomeController : Controller
{
    // 或者在这里声明
    // [HandleError]
    public ActionResult Index()
    {
        return View();
    }
}

另外一种方式是使用MVC3的Global Filters功能来注册,默认新建MVC项目在Global.asax文件里就已经有了,代码如下:

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new HandleErrorAttribute());
}

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
    );

}

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);
}

代码段里的filters.Add(new HandleErrorAttribute());设置是说整个程序所有的Controller都使用这个HandleErrorAttribute来处理错误。

注意:HandleErrorAttribute只处理500系列错误,所以404错误需要另外单独处理,稍后会提到。

下一步,我们要做的是开启web.config根目录里的customErrors(不是views目录下的那个web.config哦),代码如下:

<customerrors mode="On" defaultredirect="~/Error/HttpError">
    <error redirect="~/Error/NotFound" statuscode="404" />
</customerrors>

defaultredirect是设置为所有错误页面转向的错误页面地址,而里面的error元素可以单独定义不同的错误页面转向地址,上面的error行就是定义404所对应的页面地址。

最后一件事,就是定义我们所需要的错误页面的ErrorController:

public class ErrorController : BaseController
{
    //
    // GET: /Error/
    public ActionResult HttpError()
    {
       return View("Error");
    }
     public ActionResult NotFound()
    {
        return View();
    }
    public ActionResult Index()
    {
        return RedirectToAction("Index", "Home");
    }
}

默认Error的view是/views/shared/Error.cshtml文件,我们来改写一下这个view的代码,代码如下:

@model System.Web.Mvc.HandleErrorInfo
@{
    ViewBag.Title = "General Site Error";
}

<h2>A General Error Has Occurred</h2>

@if (Model != null)
{
    <p>@Model.Exception.GetType().Name<br />
    thrown in @Model.ControllerName @Model.ActionName</p>
    <p>Error Details:</p>
    <p>@Model.Exception.Message</p>
}

你也可以通过传递参数来重写GlobalFilter里的HandleErrorAttribte注册,单独声明一个特定的Exception,并且带有Order参数,当然也可以连续声明多个,这样就会多次处理。

filters.Add(new HandleErrorAttribute
{
    ExceptionType = typeof(YourExceptionHere),
    // DbError.cshtml是一个Shared目录下的view.
    View = "DbError",
    Order = 2
});

同步与结束语

本文已同步至目录索引:《大叔手记全集》

大叔手记:旨在记录日常工作中的各种小技巧与资料(包括但不限于技术),如对你有用,请推荐一把,给大叔写作的动力

[转载]2012年网页设计趋势

mikel阅读(985)

[转载]2012年网页设计趋势 – 创想中国(羲闻) – 博客园.

每年的网页设计趋势都会有所改变,跟时装界一样,或者说世间万物亦是如此。作为网页设计师,你必须时刻关注业界的发展和变化,随时作出应对的准备。2012年网页设计的趋势将会是怎样的呢?一起来看看flashuser博客的观点。
各位网页设计师看到了这一篇文章,应该不太意外的发现,许多内容提及的趋势其实已经正在制作,甚至已经成为了各位的设计一部分呢!

我们无法预测未来,但是我们却可以改变。由于网络的产业的变化,我们多少都会稍微的改变了原本网页设计的做法,让设计出的东西更符合客户的需求,当越来越多人这么做的时候,自然就成为了趋势。

web design trends

以下是设计师所观察到的,网页设计2012年的趋势
1. 文字艺术的盛行
flashuser viewpoint 01

文字艺术一直以来都在设计产业中活跃,不过感谢设计师这几年的努力,网页的文字艺术的活力不仅更为旺盛,更可以预测成为明年网页设计的重要元素。简 洁、强力、易于表达涵义是文字艺术的强项。此外文字艺术用于浏览受限的智慧手机与平板计算机,也非常的合适,因此绝对会成为2012年的焦点!

按编:其实台湾也大量的使用了文字艺术于不同的领域中,例如花博展、民国100年纪念、2011年设计展…等,都以文字艺术作为主要的识别象征

2. 石板字体

flashuser viewpoint 02

石板文字(Slab typefaces)一直以来用于带点艺术的设计世界中(例如T-Shirt、艺术展、设计展等)较少用于正式的场合。不过石板字体近年受到了青睐,原因是字体的呈现效果强烈,不论是一般字体或是粗体,视觉效果都十分的出色。

按编:目前英语网站的设计中,我们普遍建议使用的字体为:Time News Roman, Arial等,强烈的标题字体则可使用Arial Black或Impact。

3. 一页式网站

flashuser viewpoint 03

终于,人们发现他们不需要一个包含所有信息的复杂网站,现在的人们希望网站简洁、快速、只要几页就能解决他们的问题。一页式的网页设计正好满足了这样的族群。不用担心人们关注不到我们想呈现的内容,透过导引的方式可以让使用者跳离一页式网站,亦是流行的趋势。

良好的一页式网页设计,可以在有限的页面设计中,呈现必要的信息。目前最受欢迎的一页式网页为个人型态的中小企业,与正规的企业网站相比不仅要求明显,且易于阅读,网页内留下email或电话,便于客户询问合作事宜。

4. 自介式网站

flashuser viewpoint 04

「您好,很高兴认识你…」这样的风气已经开始影响了网页设计的潮流。网页设计者希望可以打破传统网站与用户的界线,希望可以用更为贴近用户的方式沟通,因此加入了柔性的对话,就像是跟朋友相互打招呼一样。

这样的设计并没有特殊的规则,只要轻松、贴切、感觉良好就可以了。有的设计师会将这样的口吻置于文章的标题,也有设计师用斗大的抬头来强调这个要求。

按编:其实近年科技产业大量的使用了这样的设计技巧,例如知名的苹果公司、hTC手机…等,介绍产品的口吻越来越轻松,也普遍获得大众好评

5. 超大的页首与页尾

flashuser viewpoint 05

超大的Logo、页首、页尾都能够强化视觉的表现,让浏览者留下深刻的印象,超过原本尺寸的的Logo能够让企业的印象深植于客户内心,此外页尾的扩大可以让网站塞入更多关联的信息,例如网页的链接、企业的介绍、社群网站等,是近年不少企业、专门领域网站的趋势。

[转载]B2C电子商务系统研发——商品数据模型设计

mikel阅读(806)

[转载]B2C电子商务系统研发——商品数据模型设计 – 元亨利贞 – 博客园.

  1. 基础属性
    指设计在商品表的一些基础字段。
    其中可选的设计点有:
    # 副名称:由于商品名称经常要加上一些促销信息,如本商品参与什么活动之类。但经常改动主名称
    容易导致出错,所以增加此字段来专门管理促销信息。显示时连接到主名称后即可。
    # 产品描述:产品描述建议另设计一表存放,对提高产品搜索、产品列表显示有帮助。
    # 状态:常见的状态有草稿、未发布、发布、下架等,如果是逻辑删除的,还有“已删除”状态。
  2. 价格
    如果系统支持产品SKU,那么实际价格是在产品SKU实体中管理的。
    促销价格不在这里管理,在营销管理模块统一管理。
  3. SEO相关
    集中管理各类SEO相关的信息。
  4. 商品媒体
    #主图:由于显示频繁,会直接设计在产品表中(或是冗余)。
    #多图(即附图):开发中会提供多种查看方式。
  5. 扩展属性
    这是设计最困难的部分,也是商品是否可以灵活扩展的关键。
  6. 库存
    库存这里是常见的开发迭代点。
    在研发早期,一般这里直接设计成支持零库存和单一数值库存。
    在其它功能完成后,才会对这里做扩展,开发内嵌的库存子模块或者整合外部系统。
  7. 外部关联
    商品的外部关联非常的多,这里列出了大部分,但随着系统的扩展,肯定会有新的外部关联实体。
    所以商品模块的开发,需要提供大量的外部接口或者Tag封装(如商品选取器等。)

[转载]我所理解的Delphi中的数组类型

mikel阅读(1215)

[转载]我所理解的Delphi中的数组类型 – 瓢虫Monster – 博客园.

数组可以使Object Pascal所拥有的任何数据类型,数组是一些数值的简单集合。

1
2
3
4
5
6
7
8
9
10
var
MyArray: array[0..4] of Integer; { 声明一个数组包括5个整数数值}
begin
MyArray[0] := -200; { 通过操作符[]就可以访问每个数组元素}
MyArray[1] := -100;
MyArray[2] := 0;
MyArray[3] := 100;
MyArray[4] := 200;
MyArray[0] := MyArray[1] + MyArray[4]; { MyArray[0]为-100}
end;

其MyArray在内存空间的分布,每个整数需要4个字节,因此整个数组将占20个字节的内存,如下:

0016

1、多维数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const
CArray: array[0..4] of Integer = (-20, -100, 0, 100, 200);
{ 数组常量的在声明的同时也要进行赋初值}
var
MyArray: array[0..2, 0..4] of Integer; { 两维数组的声明}
UArray: array[10..20] of Integer; { 声明了一个下界10到上界20的10个元素的数组}
X: Integer;
begin { 两种方法可以访问两维数组}
X := MyArray[1][1] + MyArray[2][1]; { 1、[X][Y]访问}
X := MyArray[1, 1] + MyArray[2, 1]; { 2、[X, Y]访问}
{ 下面的访问超出了数组范围,
将会提示“Constant expression violates subrange bounds”错误}
X := MyArray[3, 1] + MyArray[0, 5]; {注意这两个元素超出数组声明的范围}
end;

其中MyArray被声明为一个二维数组,其在内存中的分布如下:

2、上界与下界

处理数组会经常用到上界(Low)和下界(High)函数。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
var
X, I, Lower, Upper: Integer;
MyArray: array[10..20] of Integer;
begin
{ 这里假使已经将MyArray数组进行了赋初值}
Lower := Low(MyArray); { Lower的值为 10}
Upper := High(MyArray);{ Upper的值为 20}
X := 0
for I := Lower to Upper do
begin
X := X + MyArray[I]; { X将累加数组元素值的和}
end;
end;

使用上下界函数可以保证存取数组时不越界。

对了,如果换成二维数组,上下界函数如何用呢???

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var
Lower1, Upper1: Integer;{ 用来存储一维的上下界}
Lower2, Upper2: Integer;{ 用来存储二维的上下界}
MyArray: array[10..20, 1..2] of Integer;
begin
{ 这里假使已经将MyArray数组进行了赋初值}
Lower1 := Low(MyArray); { Lower的值为 10}
Upper1 := High(MyArray);{ Upper的值为 20}
ShowMessage('第一维的下界为 ' + IntToStr(Lower1) +
',上界为 ' + IntToStr(Upper1));
Lower2 := Low(MyArray[Lower1]); {获取MyArray[10]下界}
Upper2 := High(MyArray[Lower1]);{获取MyArray[10]上界}
ShowMessage('第二维的下界为 ' + IntToStr(Lower2) +
',上界为 ' + IntToStr(Upper2));
end;

两次消息框显示界面如下:

0018 0019

3、动态数组(dynamic array)

动态数组是一种在运行时分配内存的数组,一个动态数组可以变大,也可以变小。

声明一个动态数组,只要在声明时不要制定维数,就像这样:

1
2
3
4
5
6
7
8
9
10
var
SA: array of string; { 一维动态数组}
begin
{ 使用SetLength进行动态数组的空间分配,已有元素可以得到保留}
SetLength(SA, 3);
SA[0] := 'Hello World';
{ 重新分配了动态数组大小为2个元素}
SetLength(SA, 2);
ShowMessage(SA[0]); {显示为'Hello World',说明已有元素得到保留}
end;

用同样的方法也可以建立二维动态数组,如下:

1
2
3
4
5
6
7
8
9
10
var
SA: array of array of string; { 二维动态数组}
begin
{ 使用SetLength进行动态数组的空间分配,已有元素可以得到保留}
SetLength(SA, 20, 20);
SA[0][0] := 'Hello World';
{ 重新分配了动态数组大小为2个元素}
SetLength(SA, 10, 10);
ShowMessage(SA[0][0]); {显示为'Hello World',说明已有元素得到保留}
end;

动态数组建立后就可以像普通数组一样使用。

动态数组通常都是以0为基准的。

动态数组是生存期自管理的,使用完它们后没有必要释放,离开作用域后它们会被自动释放。当然,如果你想在离开作用域前就删除动态数组(比如它占用太多的内存了,需要释放掉),那就用下面的语句就可以了。

1
2
3
4
5
6
7
var
SA: array of string;
begin
SetLength(SA, 20);
SA[0] := 'Hello World';
SA := nil; { 直接把nil赋值给SA就可以了}
end;

动态数组的复制问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var
A1, A2: array of Integer;
begin
SetLength(A1, 4);
A2 := A1;
A1[0] := 1;
A2[0] := 26;
ShowMessage(IntToStr(A1[0])); { 显示结果为 26}
{
为什么显示26呢?因为A2 := A1这条赋值语句,并没有创建新的数组,仅仅只是将
A1数组的引用赋值给了A2,也就是说A2只是A1的一个别名而已,指向的都是相同东西,
因此对A2所做的任何操作都会影响到A1,如果想要完全复制创建一个新的A2数组需要
用到Copy函数,如下:
}
A2 := Copy(A1); { A1, A2现在为两个独立的数组}
A2[0] := 10;
A1[0] := 26;
ShowMessage(IntToStr(A2[0])); { 现在A2的值为10,说明完全的独立了}
A2 := nil; {释放掉A2}
A2 := Copy(A1, 1, 2); { 从元素1开始,复制2个元素到A2}
end;

以上都是在Delphi7环境中测试通过

[转载]ASP.NET MVC3 20个秘方-(18)Auto Complete(自动完成)

mikel阅读(1514)

[转载]【译】MVC3 20个秘方-(18)Auto Complete(自动完成) – 技术弟弟 – 博客园.

问题

当你查找一些特殊的东西,当你输入准确的词时,找到它可能是困难的(或者很耗时)。在输入的时候展示出结果(自动完成),使查找变得更简单。

解决方案

使用JQuery自动完成插件,更新现有图书列表页面上的搜索,当用户键入的时候立即显示结果。

讨论

自动完成插件是不会象JQuery基本库一样自动包含在MVC项目中的,所以需要做的第一件事就是的是下载插件
访问http://JQuery.com/。两个主要的文件是必需的:JavaScript文件和CSS文件。把新下载的JavaScript文件放到你MVC应用程序的script 文件夹下。CSS文件可以直接添加到您的content目录。

这个配方也将介绍在view中使用 rendering sections。在shared文件夹下layout中自动添加了2个javascript文件和1个css文件。这些是Ajax和不唐突的Ajax和 网站主css文件。每次加载的内容越多,页面视图加载越慢。与其在每个页面都去包含可能不必要的javascript和css 文件,不如在layout中添加一个新的RenderSection()。这允许特别的view在<head>标签去加载特别的 javascript或css,但不是每页都添加他们。

下边是一个更新后的Views/Shared/_Layout.cshtml,他使用了一个新的RenderSection()。

<!DOCTYPE html>

<html>
<head>
    <title>_Mobile</title>
    <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.6.2.min.js")" type="text/javascript"></script>

    <script type="text/javascript">
        $(document).ready(function () {
            if (window.innerWidth <= 480) {
                $("link[rel=stylesheet]").attr({ href: "@Url.Content("~/Content/jquery.mobile-1.0b1.min.css")" });
            }
        });
    </script>

    @RenderSection("JavaScriptAndCSS", required: false)
</head>
<body>
    <div class="page" data-role="page">
        <div id="header" data-role="header">
            <div id="title">
                <h1>My MVC Application</h1>
            </div>
            <div id="logindisplay" class="ui-bar">
                @Html.Partial("_LogOnPartial")
                [ @Html.ActionLink("English", "ChangeLanguage", "Home", new { language = "en" }, null) ]
                [ @Html.ActionLink("Français", "ChangeLanguage", "Home", new { language = "fr" }, null) ]
            </div>
            <div id="menucontainer" class="ui-bar">
                <ul id="menu">
                    <li>@Html.ActionLink("Home", "Index", "Home", null, new Dictionary<string, object> {{ "data-role", "button" }})</li>
                    <li>@Html.ActionLink("About", "About", "Home", null, new Dictionary<string, object> { { "data-role", "button" }})</li>
                </ul>
            </div>
        </div>
        <div id="main" data-role="content">
            @RenderBody()
        </div>
        <div id="footer" data-role="footer">
        </div>
    </div>
</body>
</html>

主要的CSS文件和核心的jQuery文件被留下来了,因为css在每个也都需要,并且绝大多数网页也需要jQuery。然而新的jQuery文件和不唐突的AJAX不是每个页面都需要的。

现在,有两种方式使用Autocomplete 插件:

  1. 在javascript中设置要搜索的数据。
  2. 当用户输入时通过ajax检索。

在我使用这个插件的经验看来,我发现使用解决方案1时自动完成更快。因为它并不需要每次从数据库中请求数据。然而,使用这种解决方案的限制:只有这么多字符,可传递到function中,大量的JavaScript可能会导致
用户的计算机上页面加载缓慢。经过一些试验和错误,我已经确定神奇的数字是大约40,000个结果。如果结果数量超过此,最好使用选项2;否则,始终坚持,因为搜索选项1是瞬时,而不是有轻微的延迟。

在这个例子中,将搜索书籍,我们没有超过40000,所以将使用选项1。BooksController现在必须更新, 以设置ViewBag为book title。自动完成功能需要支持一个JavaScript数组的支持,所以书将管道(|)分开。然后在view中,书将被转换到一个数组,使用 JavaScript的split()函数。当用户完成键入他们的结果,他们应该有选择完全匹配标题,因此
这个函数将被更新。如果只有1本书返回并且用户执行了搜索,它会自动重定向到本书详细介绍页面。

我们要在bookcontroller 中更新Index Action 并添加一个私有方法名为:FormatBooksForAutocomplete。

代码如下:

展开查看代码

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Linq.Dynamic;
using System.Web;
using System.Web.Mvc;
using MvcApplication.Models;
using MvcApplication.Utils;
using PagedList;

namespace MvcApplication.Controllers
{ 
    public class BooksController : Controller
    {
        private BookDBContext db = new BookDBContext();

        //
        // GET: /Books/
        [OutputCache(Duration = Int32.MaxValue, SqlDependency = "MvcApplication.Models.BookDBContext:books", VaryByParam = "sortOrder;filter;page")]
        public ActionResult Index(string sortOrder, string filter, string Keyword, int page = 1)
        {
            #region ViewBag Resources
            ViewBag.Title = Resources.Resource1.BookIndexTitle;
            ViewBag.CreateLink = Resources.Resource1.CreateLink;
            ViewBag.TitleDisplay = Resources.Resource1.TitleDisplay;
            ViewBag.IsbnDisplay = Resources.Resource1.IsbnDisplay;
            ViewBag.SummaryDisplay = Resources.Resource1.SummaryDisplay;
            ViewBag.AuthorDisplay = Resources.Resource1.AuthorDisplay;
            ViewBag.ThumbnailDisplay = Resources.Resource1.ThumbnailDisplay;
            ViewBag.PriceDisplay = Resources.Resource1.PriceDisplay;
            ViewBag.PublishedDisplay = Resources.Resource1.PublishedDisplay;
            ViewBag.EditLink = Resources.Resource1.EditLink;
            ViewBag.DetailsLink = Resources.Resource1.DetailsLink;
            ViewBag.DeleteLink = Resources.Resource1.DeleteLink;
            #endregion

            #region ViewBag Sort Params
            ViewBag.TitleSortParam = (sortOrder == "Title") ? "Title desc" : "Title";
            ViewBag.IsbnSortParam = (sortOrder == "Isbn") ? "Isbn desc" : "Isbn";
            ViewBag.AuthorSortParam = (sortOrder == "Author") ? "Author desc" : "Author";
            ViewBag.PriceSortParam = (sortOrder == "Price") ? "Price desc" : "Price";
            ViewBag.PublishedSortParam = (String.IsNullOrEmpty(sortOrder)) ? "Published desc" : "";

            // Default the sort order
            if (String.IsNullOrEmpty(sortOrder))
            {
                sortOrder = "Published desc";
            }

            ViewBag.CurrentSortOrder = sortOrder;
            #endregion

            var books = from b in db.Books select b;

            #region Keyword Search
            if (!String.IsNullOrEmpty(Keyword))
            {
                books = books.Where(b => b.Title.ToUpper().Contains(Keyword.ToUpper()) || b.Author.ToUpper().Contains(Keyword.ToUpper()));

                // Should we redirect because of only one result?
                if (books.Count() == 1)
                {
                    Book book = books.First();
                    return RedirectToAction("Details", new { id = book.ID });
                }
            }
            ViewBag.CurrentKeyword = String.IsNullOrEmpty(Keyword) ? "" : Keyword;
            #endregion

            #region Filter switch
            switch (filter)
            {
                case "NewReleases":
                    var startDate = DateTime.Today.AddDays(-14);
                    books = books.Where(b => b.Published <= DateTime.Today.Date 
                        && b.Published >= startDate
                    );
                    break;

                case "ComingSoon":
                    books = books.Where(b => b.Published > DateTime.Today.Date);
                    break;

                default:
                    // No filter needed
                    break;
            }
            ViewBag.CurrentFilter = String.IsNullOrEmpty(filter) ? "" : filter;
            #endregion

            books = books.OrderBy(sortOrder);

            int maxRecords = 1;
            int currentPage = page - 1;

            // Get all book titles
            ViewBag.BookTitles = FormatBooksForAutocomplete();

            return View(books.ToPagedList(currentPage, maxRecords));
        }

        private string FormatBooksForAutocomplete()
        {
            string bookTitles = String.Empty;
            var books = from b in db.Books select b;

            foreach (Book book in books)
            {
                if (bookTitles.Length > 0)
                {
                    bookTitles += "|";
                }

                bookTitles += book.Title;
            }

            return bookTitles;
        }

        //
        // GET: /Books/Details/5

        public ActionResult Details(int id = 0, string bookTitle = "")
        {
            Book book = db.Books.Find(id);
            return View(book);
        }

        //
        // GET: /Books/Create

        public ActionResult Create()
        {
            return View();
        } 

        //
        // POST: /Books/Create

        [HttpPost]
        public ActionResult Create(Book book, HttpPostedFileBase file)
        {
            if (ModelState.IsValid)
            {
                // Upload our file
                book.Thumbnail = FileUpload.UploadFile(file);

                db.Books.Add(book);
                db.SaveChanges();
                return RedirectToAction("Index");  
            }

            return View(book);
        }
        
        //
        // GET: /Books/Edit/5
 
        public ActionResult Edit(int id)
        {
            Book book = db.Books.Find(id);
            return View(book);
        }

        //
        // POST: /Books/Edit/5

        [HttpPost]
        public ActionResult Edit(Book book, HttpPostedFileBase file)
        {
            if (ModelState.IsValid)
            {
                // Delete old file
                FileUpload.DeleteFile(book.Thumbnail);

                // Upload our file
                book.Thumbnail = FileUpload.UploadFile(file);

                db.Entry(book).State = EntityState.Modified;
                db.SaveChanges();
                return RedirectToAction("Index");
            }
            return View(book);
        }

        //
        // GET: /Books/Delete/5
 
        public ActionResult Delete(int id)
        {
            Book book = db.Books.Find(id);
            return View(book);
        }

        //
        // POST: /Books/Delete/5

        [HttpPost, ActionName("Delete")]
        public ActionResult DeleteConfirmed(int id)
        {            
            Book book = db.Books.Find(id);
            
            // Delete old file
            FileUpload.DeleteFile(book.Thumbnail);

            db.Books.Remove(book);
            db.SaveChanges();
            return RedirectToAction("Index");
        }

        protected override void Dispose(bool disposing)
        {
            db.Dispose();
            base.Dispose(disposing);
        }
    }
}

最后book/index view需要更新去初始化jQuery的自动完成。要做的第一件事是使用@节标记,包括必要的JavaScript和CSS文件。接下来,以前创建的搜索 文本框更新设置一个键的IDwordSearch。最后,JavaScript代码添加在视图的底部去在搜索文本框上建立自动完成功能。此 JavaScript是有意添加在view的底部,以确保完全呈现给用户,因为在用户的电脑上建立数据是一项工作,Javascript处理可能会“堵 塞”页面加载。(译者:先呈现数据再执行javascript,js不是像传统那样放在head标签里),这取决于结果的数量。代码如下:

展开查看代码

@model PagedList.IPagedList<MvcApplication.Models.Book>

@if (IsAjax)
{
    Layout = null;
}

@section JavascriptAndCSS {
<link rel="stylesheet" href="@Url.Content("~/Content/jquery.autocomplete.css")" type="text/css" />
<script type="text/javascript" src="@Url.Content("~/Scripts/jquery.autocomplete.js")"></script>
<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
}

<h2>@MvcApplication4.Resources.Resource1.BookIndexTitle</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<p>
    Show:
    @if (ViewBag.CurrentFilter != "")
    {
        @Ajax.ActionLink("All", "Index", new { sortOrder = ViewBag.CurrentSortOrder, Keyword = ViewBag.CurrentKeyword }, new AjaxOptions { UpdateTargetId = "main" })
    }
    else
    {
        @:All
    }
    &nbsp; | &nbsp;
    @if (ViewBag.CurrentFilter != "NewReleases")
    {
        @Ajax.ActionLink("New Releases", "Index", new { filter = "NewReleases", sortOrder = ViewBag.CurrentSortOrder, Keyword = ViewBag.CurrentKeyword }, new AjaxOptions { UpdateTargetId = "main" })
    }
    else
    {
        @:New Releases
    }
    &nbsp; | &nbsp;
    @if (ViewBag.CurrentFilter != "ComingSoon")
    {
        @Ajax.ActionLink("Coming Soon", "Index", new { filter = "ComingSoon", sortOrder = ViewBag.CurrentSortOrder, Keyword = ViewBag.CurrentKeyword }, new AjaxOptions { UpdateTargetId = "main" })
    }
    else
    {
        @:Coming Soon
    }
</p>
@using (Html.BeginForm())
{
    @:Search: @Html.TextBox("Keyword", (string)ViewBag.CurrentKeyword, new { id = "KeywordSearch" }) <input type="submit" value="Search" />
}
@Html.Partial("_Paging")
<table>
    <tr>
        <th>
            @Ajax.ActionLink("Title", "Index", new { sortOrder = ViewBag.TitleSortParam, filter = ViewBag.CurrentFilter, Keyword = ViewBag.CurrentKeyword }, new AjaxOptions { UpdateTargetId = "main" })
        </th>
        <th>
            @Ajax.ActionLink("Isbn", "Index", new { sortOrder = ViewBag.IsbnSortParam, filter = ViewBag.CurrentFilter, Keyword = ViewBag.CurrentKeyword }, new AjaxOptions { UpdateTargetId = "main" })
        </th>
        <th>
            Summary
        </th>
        <th>
            @Ajax.ActionLink("Author", "Index", new { sortOrder = ViewBag.AuthorSortParam, filter = ViewBag.CurrentFilter, Keyword = ViewBag.CurrentKeyword }, new AjaxOptions { UpdateTargetId = "main" })
        </th>
        <th>
            Thumbnail
        </th>
        <th>
            @Ajax.ActionLink("Price", "Index", new { sortOrder = ViewBag.PriceSortParam, filter = ViewBag.CurrentFilter, Keyword = ViewBag.CurrentKeyword }, new AjaxOptions { UpdateTargetId = "main" })
        </th>
        <th>
            @Ajax.ActionLink("Published", "Index", new { sortOrder = ViewBag.PublishedSortParam, filter = ViewBag.CurrentFilter, Keyword = ViewBag.CurrentKeyword }, new AjaxOptions { UpdateTargetId = "main" })
        </th>
        <th></th>
    </tr>

@foreach (var item in Model)
{
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.Title)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Isbn)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Summary)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Author)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Thumbnail)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Price)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Published)
        </td>
        <td>
            @Html.ActionLink("Edit", "Edit", new { id = item.ID }) |
            @Html.ActionLink("Details", "Details", new { id = item.ID }) |
            @Html.ActionLink("Delete", "Delete", new { id = item.ID })
        </td>
    </tr>
}

</table>

@Html.Partial("_Paging")

<script type="text/javascript">
    $(document).ready(function () {
        var data = "@ViewBag.BookTitles".split("|");
        $("#KeywordSearch").autocomplete(data);
    });
</script>

为了实施选项2,一个Ajax搜索,而不是传递数据数组到自动完成函数,您可以传递一个URL。URL将需要接受查询字 符串变量:q。这包含用户输入的搜索值。这将用于执行书本上包含部分匹配的搜索,并返回以分隔符分隔的字符串。JQuery文档中含有较多的这样的成品例 子,也有其他的例子,去更新的输出结果(可能包括书的封面的缩略图)。

另请参阅

Jquery.Autocomplete, RenderSection

[转载]ASP.NET MVC3 20个秘方-(17)卷帘式分页

mikel阅读(1490)

[转载]【译】MVC3 20个秘方-(17)卷帘式分页 – 技术弟弟 – 博客园.

问题

今天很多网站与数据库交互。如果您的网站接收大量的流量,SQL查询来检索数据,是相当激烈的。更重要的是因为普通用户 点击一个在15秒内到达您的网站的链接, 检索和生成内容的工作可能是不必要的,尤其是当内容是“地域折叠”(不滚动是不可见的)。为了帮助解决这个问题,内容将被“按需”载入。足够的内容将被载 入,使页面感觉起来是随用户向下滚动阅读递增的,更多的内容将在不影响用户体验的场景下填充。

解决方案

使用异步controller与JQuery按需加载内容,当用户开始通过网站的内容滚动时进一步加载内容。

讨论

异步controller可能在许多MVC应用中未充分利用。最有可能的是因为人们不知道他们,更重要的是,不知道何时使用它们。以下是摘录见MSDN网站:

“在应用中,线程饥饿可能会发生,您可以配置action 异步处理。异步请求和同步请求过程需要相同的时间例如,如果一个请求,使得网络调用需要两秒钟来完成的,请求需要两秒钟,无论是执行同步或异步。然而,在 一个异步调用,当服务器响应等待它的第一次请求时完成时他响应其他的请求没有被阻塞。因此,当有许多请求调用长时间运行的操作时,异步请求会防止请求排 队。“

在这个例子里,异步请求是完美的解决方案。因为他会让IIS服务器有能力去处理更多重要的请求,比如一个新的用户第一次访问网站。其中,加载用户点 播内容是不太重要,因为大多数人甚至不关注被加载额外的内容。在一个典型的社交网站,大多数活动可能包含用户的意见。在以前的秘方中,创建了一个评论的功 能。在这个例子中,将更新网站的网页,列出最近的评论。足够的评论会被显示,所以会出现滚动条。一旦用户开始滚动,一个Ajax请求异步 controller将检索其他评论。

首先Home/Index view 需要更新去显示最近的评论。为了提供一些评论的上下文内容,关于书的基础详情也将被显示为导航到图书的链接。所以这个view将简单的调用view 中的render function会在下边创建:

@model IEnumerable<MvcApplication.Models.BookComment>
@{
    ViewBag.Title = "Home Page";
}
<h2>@ViewBag.Message</h2>
<p>
    To learn more about ASP.NET MVC visit <a href="http://asp.net/mvc" title="ASP.NET MVC Website">
        http://asp.net/mvc</a>.
</p>
<script type="text/javascript">
var lastY = 0;
var currentY = 0;
var page = 1;
var maxPages = @ViewBag.maxPages;
$(window).scroll(function () {
if (page < maxPages) {
currentY = $(window).scrollTop();
if (currentY - lastY > 200 * (page - 1)) {
lastY = currentY;
page++;
$.get('CommentFeed/Comments?page=' + page,
function(data) {
$('#comments').append(data);
});
}
}
});
</script>
<div id="comments">
    <h2>
        Recent Comments</h2>
    @Html.Partial("../CommentFeed/Comments", Model)
</div>

在上面的例子,执行滚动窗口时也有一些比较复杂的JavaScript代码会执行。一些全局JavaScript变量被定义去保持监控当前的“Y” 滚动的位置,最后的“Y”滚动位置和当前被检索的页面。当窗口的scrollTop位置减去最后的滚动位置是大于一个具体的数字,通过Ajax检索新书评 论并附加到评论列表。

你将根据你自己的网站去根据矫正那个具体的数字,基于内容的高度,要确保新的内容总是要提前检索。
下一步,HomeController需要更新检索图书评论列表。 评论在降序排序,以确保最新的创建日期评论首先显示。为了防止激烈的数据库负载,全部 评论将减少到一个小数目。这应该根据你的网站去调节,以确保有足够的内容,导致滚动。在下面的例子,建议被限制在3。分页的最大数也取决于评论总数于除以 3。一旦最大的评论数已经返回,最大分页数是用来防止进一步的Ajax调用。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Globalization;
using System.Data.Entity;
using MvcApplication.Models;

namespace MvcApplication.Controllers
{
    public class HomeController : Controller
    {
        private BookDBContext db = new BookDBContext();

        public ActionResult Index()
        {
            ViewBag.Message = "Welcome to ASP.NET MVC!";

            // Get our recent comments
            var bookcomments = db.BookComments.Include(b => b.Book).OrderByDescending(b => b.Created).Take(3);
            var count = db.BookComments.Count();
            ViewBag.maxPages =  count / 3 + 1;

            return View(bookcomments);
        }

        public ActionResult ChangeLanguage(string language)
        {
            Session["CurrentLanguage"] = new CultureInfo(language);
            return Redirect("Index");
        }

        public ActionResult About()
        {
            return View();
        }

        public ActionResult MobileTest()
        {
            return View();
        }

        public ActionResult MobileTest2()
        {
            return View();
        }
    }
}

同样的功能需要被复制到一个新的异步controller。控制器文件夹选中,右键单击并选择“添加→控制器。新
控制器将被命名为CommentFeedController。该控制器不需要脚手架模板功能,下拉,选择空
控制器,然后按添加。

这个控制器会看起来与一个典型的控制器稍有不同。使用异步控制器,一个view将分成两个函数。第一个函数执行的异步
请求(例如,检索的view)。第二个函数接收结果,异步调用和返回或显示的结果。

提示:在下面的例子,呈现局部视图。在某些应用中,它可能是有益的,以减少网络流量,返回一个JSON结果,让JavaScript代码处理与显示。但是,要简化这个例子,重点放在异步控制器,前者将用于返回一个partial view。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcApplication.Models;
using System.Data.Entity;
namespace MvcApplication.Controllers
{
    public class CommentFeedController : AsyncController
    {
        private BookDBContext db = new BookDBContext();
        public void CommentsAsync(int page)
        {
            AsyncManager.OutstandingOperations.Increment();
            AsyncManager.Sync(() =>
            {
                var bookcomments = db.BookComments.Include(
                b => b.Book).OrderByDescending(b =>
                b.Created).Skip(page * 3).Take(3);
                AsyncManager.Parameters["bookcomments"] =
                bookcomments;
                AsyncManager.OutstandingOperations.Decrement();
            });
        }
        public ActionResult CommentsCompleted(
        IEnumerable<BookComment> bookcomments)
        {
            return PartialView(bookcomments);
        }
    }
}

第一个 函数,CommentsAsync,接收从javascript传入的当前页面,并且用这个值去检索接下来的三个评论。然后通过异步方法,检索评论并且传 递一个变量到第二个函数。最终的事是执行AsyncManager.OutstandingOperations.Decrement()方法。 OutstandingOperations(未解决操作)的increment(递增)和decrement(递减)的匹配是很重要的。否则,当他们不 匹配时,sync manager 将取消请求,这可以组织永不休止的请求。

第二个函数接收book comments 并且返回一个partial view。这和Home/Index view一样。在这个过程的最后一个步骤是创建partial view。右击文件夹,添加新文件夹。这个文件夹应该命名为CommentFeed去匹配controller的名字。选择这个文件夹,右击,点AddView 命名为Comments—–在添加它之前确定去检查Partial view。

@model IEnumerable<MvcApplication.Models.BookComment>
@foreach (var item in Model) {
<h3><a href="@Url.Action("Details", "Books", new {ID=item.Book.ID } )">
@Html.DisplayFor(modelItem => item.Book.Title)
</a></h3>
<h4>Comment Posted: @Html.DisplayFor(
modelItem => item.Created)</h4>
<p>@MvcHtmlString.Create(Html.Encode(item.Comment).Replace(
Environment.NewLine, "<br />"))</p>
}

上边的view遍历comments,并首先显示书的标题和详细信息页面的链接,然后创建评论的日期,最后创建comment本身。由于View里可能包含换行符,用<br/>替代每个Evironment.NewLine去匹配评论输入间距。

另请参阅

Asynchronous controllers

[转载]ASP.NET MVC 3 开发的20个秘诀(十)[20 Recipes for Programming MVC 3]:通过关键字进行列表搜索

mikel阅读(926)

[转载][翻译]ASP.NET MVC 3 开发的20个秘诀(十)[20 Recipes for Programming MVC 3]:通过关键字进行列表搜索 – O2DS – 博客园.

议题

当排序、分页和筛选不能够帮助用户找到所需的内容,下一步最好的方法就是让用户输入他所想要查找的内容。

解决方案

使用Html.Helper创建表单和文本输入框,在筛选器选择结果的基础上用Linq Libary使用用户输入的关键字进行查询。

讨论

就像之前的秘诀所做的,添加关键字搜索需要修改Books和Index视图以及BooksController控制器。在视图中创建一个新的表单, 添加新的输入关键字的文本框。此外,为了在用户搜索关键字时,保持用户选择的排序、筛选器选项就需要重新编辑原有代码。下面是修改后的Book和 Index视图:

@model PagedList.IPagedList<MvcApplication4.Models.Book>           
<h2>@MvcApplication4.Resources.Resource1.BookIndexTitle</h2>
<p>
    @Html.ActionLink("Create New", "Create")
</p>
<p>
    Show:
    @if (ViewBag.CurrentFilter != "")
    {
        @Html.ActionLink("All", "Index", new {
        sortOrder = ViewBag.CurrentSortOrder,
        Keyword = ViewBag.CurrentKeyword })
    }
    else
    {
        @:All
    }
    &nbsp; | &nbsp;
    @if (ViewBag.CurrentFilter != "NewReleases")
    {
        @Html.ActionLink("New Releases", "Index", new {
        filter = "NewReleases",
        sortOrder = ViewBag.CurrentSortOrder,
        Keyword = ViewBag.CurrentKeyword })
    }
    else
    {
        @:New Releases
    }
    &nbsp; | &nbsp;
    @if (ViewBag.CurrentFilter != "ComingSoon")
    {
        @Html.ActionLink("Coming Soon", "Index", new {
        filter = "ComingSoon",
        sortOrder = ViewBag.CurrentSortOrder,
        Keyword = ViewBag.CurrentKeyword })
    }
    else
    {
        @:Coming Soon
    }
</p>

@using (Html.BeginForm())
{
    @:Search: @Html.TextBox("Keyword")
    <input type="submit" value="Search" />
}
    
@Html.Partial("_Paging")

<table>
    <tr>
        <th>
            @Html.ActionLink("Title", "Index", new {
                sortOrder = ViewBag.TitleSortParam,
                    filter = ViewBag.CurrentFilter,
                    Keyword = ViewBag.CurrentKeyword })
        </th>
        <th>
            @Html.ActionLink("Isbn", "Index", new {
                sortOrder = ViewBag.IsbnSortParam,
                    filter = ViewBag.CurrentFilter,
                    Keyword = ViewBag.CurrentKeyword })
        </th>
        <th>
            Summary
        </th>
        <th>
            @Html.ActionLink("Author", "Index", new {
                sortOrder = ViewBag.AuthorSortParam,
                    filter = ViewBag.CurrentFilter,
                        Keyword = ViewBag.CurrentKeyword })
        </th>
        <th>
            Thumbnail
        </th>
        <th>
            @Html.ActionLink("Price", "Index", new {
                sortOrder = ViewBag.PriceSortParam,
                    filter = ViewBag.CurrentFilter,
                        Keyword = ViewBag.CurrentKeyword })
        </th>
        <th>
            @Html.ActionLink("Published", "Index", new {
                sortOrder = ViewBag.PublishedSortParam,
                    filter = ViewBag.CurrentFilter,
                        Keyword = ViewBag.CurrentKeyword })
        </th>
        <th></th>
    </tr>
    @foreach (var item in Model)
    {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Isbn)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Summary)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Author)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Thumbnail)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Published)
            </td>
            <td>
                @Html.ActionLink("Edit",
                    "Edit", new { id = item.ID }) |
                @Html.ActionLink("Details",
                    "Details", new { id = item.ID }) |
                @Html.ActionLink("Delete",
                    "Delete", new { id = item.ID })
            </td>
        </tr>
    }
</table>
@Html.Partial("_Paging")

需要修改分页视图,以维持目前搜索的关键字:

<p>
    @if (Model.HasPreviousPage)
    {
        @Html.ActionLink("<< First", "Index", new {
            page = 1,
            sortOrder = ViewBag.CurrentSortOrder,
            filter = ViewBag.CurrentFilter,
            Keyword = ViewBag.CurrentKeyword })
        @Html.Raw("&nbsp;");
        @Html.ActionLink("< Prev", "Index", new {
            page = Model.PageNumber - 1,
            sortOrder = ViewBag.CurrentSortOrder,
            filter = ViewBag.CurrentFilter,
            Keyword = ViewBag.CurrentKeyword })
    }
    else
    {
        @:<< First
        @Html.Raw("&nbsp;");
        @:< Prev
    }
    &nbsp;&nbsp;
    @if (Model.HasNextPage)
    {
        @Html.ActionLink("Next >", "Index", new {
            page = Model.PageNumber + 1,
                sortOrder = ViewBag.CurrentSortOrder,
                    filter = ViewBag.CurrentFilter,
                        Keyword = ViewBag.CurrentKeyword })
        @Html.Raw("&nbsp;");
        @Html.ActionLink("Last >>", "Index", new {
            page = Model.PageCount,
                sortOrder = ViewBag.CurrentSortOrder,
                    filter = ViewBag.CurrentFilter,
                        Keyword = ViewBag.CurrentKeyword })
    }
    else
    {
        @:Next >
        @Html.Raw("&nbsp;")
        @:Last >>
    }
</p>

最后,需要修改BooksController控制器。在下面这个例子中,修改Index()方法,添加一个新的输入参数,可以接受用户输入关键字搜索书籍标题和作者。如果需要添加其他字段的搜索,只需要修改下面的例子,包含额外的字段:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Linq.Dynamic;
using System.Web;
using System.Web.Mvc;
using MvcApplication4.Models;
using MvcApplication4.Utils;
using PagedList;

namespace MvcApplication4.Controllers
{
    public class BooksController : Controller
    {
        private BookDBContext db = new BookDBContext();
        //
        // GET: /Books/
        public ViewResult Index(string sortOrder, string filter,
        string Keyword, int page = 1)
        {
            #region ViewBag Resources
            ...
            #endregion
            #region ViewBag Sort Params
            ...
            #endregion
            var books = from b in db.Books select b;
            #region Keyword Search
            if (!String.IsNullOrEmpty(Keyword))
            {
                books = books.Where(b =>
                b.Title.ToUpper().Contains(Keyword.ToUpper())
                || b.Author.ToUpper().Contains(
                Keyword.ToUpper()));
            }
            ViewBag.CurrentKeyword =
            String.IsNullOrEmpty(Keyword) ? "" : Keyword;
            #endregion
            #region Filter Switch
            ...
            #endregion
            int maxRecords = 1;
            int currentPage = page - 1;
            return View(books.ToPagedList(currentPage,
            maxRecords));
        }
        ...
    }

}

参考

原书地址 书籍源代码

[转载]XML代码生成器——XmlFactory 简介(一)

mikel阅读(1125)

[转载]XML代码生成器——XmlFactory 简介(一) – 润年 – 博客园.

软件开发中经常要和第三方应用交互数据,特别是在银行、电信行业,这种需求更是必不可少,往往一个系统要和三五个其它系统交互数据,而数据交换的报文经常采用Xml格式。 Xml结构严谨,利于人的阅读,但格式较复杂,用代码解析和拼装有些繁琐。无论是把Xml转化成内存中的实体类对象,还是把实体类对象序列化为XML,都是个力气活,而且耗费时间。 还好,问题总是有解决的办法,这里向大家介绍一款在线代码生成器:XmlFactary。如果你有现成的Xml文档,那么大部分情况下你不需要手工写代码来处理Xml与实体对象间的转换。

XmlFactary这款代码生成器,通过分析给定的Xml文本,称之为样本Xml,将元素映射为实体类,通过汇总每一个元素的全部子属性(Attribute),子元素(Element),及文本值(value),来形成实体类的属性。名称相同的元素,将映射为一个实体类。 下面用实例简介一下大致的用法,抛砖引玉一下。

第一步就是先准备一个XML文档,想来想去就用大家都比较熟悉的RSS格式吧。用代码生成器分析下面的Xml文档后,将默认生成三个类:Rss,Channel,Item,这三个类分别代表XML元素:<rss> <channel> <item>。

<rss version=”2.0″>
<channel>
<title>参考消息电子版</title>
<link>http://www.ckxx.info/</link>

<item>
<guid>http://www.ckxx.info/other1/201111/7-55584.html</guid>
<title>伊朗核问题临近最后摊牌</title>
<description>伊朗核问题临近最后摊牌</description>
<link>http://www.ckxx.info/other1/201111/7-55584.html</link>
<pubDate>2011-11-07</pubDate>
</item>
<item>
<guid>http://www.ckxx.info/other1/201111/7-55583.html</guid>
<title>日本天皇因病住院</title>
<author>http://www.ckxx.info</author>
<description>日本天皇因病住院</description>
<link>http://www.ckxx.info/other1/201111/7-55583.html</link>
</item>
</channel>
</
rss>

我喜欢目标驱动的学习方式,在学习一项技术前,最好先知道这个技术的应用结果以及能带来什么帮助,所以我先不讲如何做,而是先把做完的结果展示给大家。

这里:http://www.codingfactory.net/Page/XmlFactory/OutPut/XmlCSharp.aspx?id=8908 是根据以上XML生成的全部代码,大家最好先了解一下代码内容,再看下面的具体操作方法不迟。

另外,这款代码生成器功能较灵活,每一个步骤都有不少可配置项, 本文并不一一展开,因为过于琐碎的细节会影响我们对主流程的理解。 有关代码生成器的详细说明,可参考官方帮助文档,有兴趣的朋友可自行了解。

帮助:http://www.codingfactory.net/Page/XmlFactory/homepage/index.htm

操作

1.打开这个网址(是个flash文件,400多k,有点大要稍等一会儿): http://www.codingfactory.net/Page/XmlFactory/client/XmlFactory_Flex.html 把上面的Xml粘到样本Xml中。

2.点右上方的 NEXT 铵钮,页面跳转到 元素/子值 页签。该页面将按元素名称汇总显示每一元素的子值,你可以在此调整Xml文档的元素结构。

左侧 元素列表 显示所有XML元素(Element),右侧 子值列表 显示选中元素下的所有子值。 注意<channel>元素下有2个<item>子元素,所以,它的 多个 列中是被勾选中的,生成代码时,这一项会被映射为List<T>形式的类属性。 当然你也可以把title,link前面的勾打上,这样生成代码时,这两项也会变成List形式。

子值列表中 多个 列是蓝色字体的列头,蓝色字体表示该列可以编辑。你还可以通过 添加子值删除子值 等功能来调整Xml元素的子值结构;如果某个元素,你根本不需要,直接点 删除元素 就成了。对于本例,我们在这一页不用做任何改动。

3.再点击 NEXT 跳转到 类名称 页面。该页面中,你可以为Xml元素指定实体类名称,表示你要把元素映射为这个名称的类。

注意:不是所有的Xml元素都要映射为实体类。比如本例中的<title>,<link>元素,它们只包含一个子值:[value], 当然没必要为这种元素定义实体类,因为基本数据类型(int,bool,string……)就可以描述这个元素的值了。 所以,类名称 那一列空着什么都不填就成了,表示不将这个元素映射为类。

这个页签中三个列表的字段完全一致,区别在于:

a.最上方列表中,每一个元素都必须映射为类,也就是说 类名称 那一列中必须填一个合法的类名。

b.最下方列表中所有元素都只含有一个子值,这种元素没必要映射成类,因为基本数据类型(int,double,string,datetime)就可以代表其内容。所以 类名称 那列应该空着。

c.中间列表中的元素建议映射为类。你在 类名称 中填入类名,这个元素就映射为类;空着的话,这个元素将被脱壳(脱壳的概念有些复杂,详细说明请参考官方帮助文档)。

这部分有些复杂,不过没关系,对于我们的例子而言,这一页什么都不用做,因为一般情况下默认设置就十分合理,你直接点 NEXT 就成了。

4.点击 NEXT 跳转到 类属性 页面。这一页你要为Xml元素的子值指定对应的属性名称和数据类型。

代码生成器根据样本Xml中的内容,提供默认的数据类型,如果与你的实际需求不符,你可以展开 数据类型 列的下拉列表,从中选择你需要的基本数据类型。 对于已经映射为类的子值,则不可变更其数据类型。例如:本例中元素<item>,已经映射为类Item,所以类Channel的属性Items,必须是Item类型的,不可变更。 点击 属性名称 列的”item”单元格手工变为Items,表示生成的实体类Channel中有一个List<Item> Items 属性。

5.点击 NEXT 跳转到 预览 页面。到了这个页面,你已能看到生成的实体类定义了。如果发现和预期的有所出入,可以点击之前的页签,重新进行配制。

预览页面有几个可配置项,分别说一下:

贫血模式 生成的实体类中,只包含属性。实体类与Xml的转换函数在其它类中实现(“转换类名称”中指定的类)
充血模式 生成的实体类中,不仅包含属性,还包含实体类与Xml的转换函数
包/命名空间 实体类所属的命名空间
转换类名称 如果采用贫血模式,实体类与Xml的转换函数将实现在此处指定的类中。

6.保留默认配置,最后一次点 NEXT ,弹出 生成代码 页面。这个页面大家之前已经看过了,它包含三部分代码:

EntityDefine 实体类定义,如果为充血模式则还包含转换函数。
AssistFunction 包含二个辅助函数:LoadFromFile SaveToFile。大家看一下调用方式,就知道成生的代码如何使用了。
Xml Entity Convert 实体对象与Xml转换函数(仅在贫血模式下才有内容)

小结

好了,终于写完了。总结一下大体流程就这几步:

1. 样本Xml页签中录入用于分析的Xml

2. 元素/子值页签用于调整配置Xml结构

3. 类名称页签用于指定类名称

4. 类属性页签用于指定类属性

5. 预览页签用于配置代码生成项

最后,说说不足:

1.生成的代码是基于Dom模型,不利于处理大文件(但多大算大呢?那得看硬件水平了,呵呵)

2.目前是测试版,功能上有限制,样本Xml大小限于2K,一次最多生成6个类

[转载]android 程序开发的插件化 模块化方法 之一

mikel阅读(1758)

[转载]android 程序开发的插件化 模块化方法 之一 – 黑暗伯爵 – 博客园.

Android的项目开发中,都会遇到后期功能拓展增强与主程序代码变更的现实矛盾,也就是程序的灵活度。

由于linux平台的安全机制,再加上dalvik的特殊机制,各种权限壁垒,使得开发一个灵活多变的程序,变得比较困难,不像pc平台下那么容易。

瞅瞅elipse的插件,瞅瞅360的插件,在Android下,我们一开始很难写好一个主程序,然后通过插件机制来应对以后的功能拓展,于是程序变得不那么灵活多变了。

比如一款android下的安全软件,新版本增加了一个功能,如短信拦截,往往会因为一个模块的增加,而重新编译一个apk包,这样周而复始,哪怕只增加50kb的功能代码,用户也需要升级一个完整的apk,往往是5~6M的体积。

最近思来想去,想到一个方法,既然tencent qq在android下面可以以apk的形式来换皮肤,这资源文件的拓展都可以这样简便的搞,为何功能性的拓展就不可以?

想出来了两种解决方案。

先来说说第一种。

demo下载在最后

先说分析思路。

android下,默认的情况是,每个apk相互独立的,基本上每个应用都是一个dalvik虚拟机,都有一个uid,再配合上linux本身的权限机 制,使得apk互通很难直接进行。但作为一个独立应用的集成,不管多少个apk,都可以并为一个单独的dalvik虚拟机,直观的反映给开发人员就是在 shell下列出进程,那几个apk同时加载后,会一个进程存在。

这主要就是工程的清单文件 Mainfest中配置了,只需要一句话,以我的测试demo为例:

....

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="org.igeek.plugintest.main"
      <!-- 就是这句关键代码 -->
      android:sharedUserId="org.igeek.plugintest"
      android:versionCode="1"      
      android:versionName="1.0">

.....

在上面的代码中,android:sharedUserId是指共用一个uid,也就是,凡是这个属性相同的工程,都会共用同一个uid,这样,权限壁垒就消除了,dalvik也会融合为一个,可以测试一下,写几个工程,没有这个属性和有这个属性的情况下,同时运行,在列出当前进程,就直观的说明了。

程序拓展的插件化,当然需要一个主程序,主程序是实现基本功能,以及UI,还有插件的检索以及插件的调用。

这里贴出我demo中的主activity代码:

View Code

  1 public class AndoirdpluginActivity extends ActivityGroup implements OnClickListener ,OnScrollCompleteListener{
  2     private LinearLayout llMainLayout;
  3     
  4     //workspace,看看luncher的源码,这个就是桌面那个多屏的实现
  5     private WorkSpace wkMain;
  6     private Button btnFindPlugins;
  7     private CheckBox chbAttachMain;
  8     
  9     private LocalActivityManager m_ActivityManager;
 10     
 11     //这个bean的集合,就相当于插件的描述集合
 12 //每个bean也就是一个插件的各种描述
 13     private List<PluginBean> plugins;
 14     
 15     @Override
 16     public void onCreate(Bundle savedInstanceState) {
 17         super.onCreate(savedInstanceState);
 18         setContentView(R.layout.main);
 19         
 20         llMainLayout=(LinearLayout) findViewById(R.id.main_llMainLayout);
 21         wkMain=(WorkSpace) findViewById(R.id.main_wkMain);
 22         btnFindPlugins=(Button) findViewById(R.id.main_btnFindPlugins);
 23         chbAttachMain=(CheckBox) findViewById(R.id.main_chbAttachMain);
 24         
 25         m_ActivityManager = getLocalActivityManager();
 26         
 27         wkMain.setOnScrollCompleteLinstenner(this);
 28         btnFindPlugins.setOnClickListener(this);
 29     }
 30 
 31     @Override
 32     public void onClick(View v) {
 33         attachPlugin(findPlugins());
 34         btnFindPlugins.setVisibility(View.GONE);
 35     }
 36     
 37     /**
 38      * 加载插件列表
 39      * @param plugins
 40 */
 41     private void attachPlugin(final List<PluginBean> plugins){
 42         Log.e("ydt", "   ");
 43         Log.e("ydt", "----- 列出插件");
 44         this.plugins=plugins;
 45         for(final PluginBean plugin:plugins){
 46             Button btn=new Button(this);
 47             btn.setTextColor(Color.RED);
 48             btn.setText(plugin.getLabel());
 49             
 50             llMainLayout.addView(btn);
 51             //添加事件
 52             btn.setOnClickListener(new OnClickListener() {
 53                 
 54                 @Override
 55                 public void onClick(View v) {
 56                     boolean isAttack=chbAttachMain.isChecked();
 57                     
 58                     Intent it=new Intent();
 59                     it.setAction(plugin.getPakageName());
 60                     
 61                     //是否附加为view
 62                     if(isAttack){
 63                         //这里偷下懒,这是演示插件作为view附加到主程序中的
 64                         for(PluginBean plugin:plugins){
 65                             
 66                             Intent itt=new Intent();
 67                             itt.setAction(plugin.getPakageName());
 68                             ViewGroup view=(ViewGroup) (m_ActivityManager.startActivity("", itt)).getDecorView();
 69                             wkMain.addView(view);
 70                         }
 71                         //一次性附加完毕算了,然后把按钮都删了,看着清净,这几个不是重点
 72                         llMainLayout.removeAllViews();
 73                         chbAttachMain.setVisibility(View.GONE);
 74                         wkMain.setToScreen(0);
 75                     }else{
 76                         //这里,不会把插件的窗体附加到主程序中,纯粹无用的演示
 77                         startActivity(it);
 78                     }
 79                 }
 80             });
 81             
 82         }
 83     }
 84     
 85     /**
 86      * 查找插件
 87      * @return
 88 */
 89     private List<PluginBean> findPlugins(){
 90         
 91         List<PluginBean> plugins=new ArrayList<PluginBean>();
 92         
 93         
 94         //遍历包名,来获取插件
 95         PackageManager pm=getPackageManager();
 96         
 97         
 98         List<PackageInfo> pkgs=pm.getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES);
 99         for(PackageInfo pkg    :pkgs){
100             //包名
101             String packageName=pkg.packageName;
102             String sharedUserId= pkg.sharedUserId;
103             
104             //sharedUserId是开发时约定好的,这样判断是否为自己人
105             if(!"org.igeek.plugintest".equals(sharedUserId)||"org.igeek.plugintest.main".equals(packageName))
106                 continue;
107             
108             //进程名
109             String prcessName=pkg.applicationInfo.processName;
110             
111             //label,也就是appName了
112             String label=pm.getApplicationLabel(pkg.applicationInfo).toString();
113             
114             PluginBean plug=new PluginBean();
115             plug.setLabel(label);
116             plug.setPakageName(packageName);
117             
118             plugins.add(plug);
119         }
120         
121         
122         return plugins;
123         
124     }
125 
126  
127     /**
128      * WorkSpace滚动到那个屏,会触发这个事件
129      * 而worksapce中每一屏又是一个插件
130      * 这个事件是用来列出当前屏幕插件所提供的应用,并且让用户调用
131 */
132     @Override
133     public void onScrollComplete(final ScrollEvent e) {
134         try {
135             final Context context = createPackageContext(plugins.get(e.curScreen).getPakageName(), Context.CONTEXT_INCLUDE_CODE|Context.CONTEXT_IGNORE_SECURITY);
136             llMainLayout.removeAllViews();
137             //这几行,通过反射获取了当前插件的描述信息,如同大部分框架的xml一样,这里算是模拟了一下IOC控制反转
138             Class clazz=context.getClassLoader().loadClass(plugins.get(e.curScreen).getPakageName()+".PluginApplication");
139             Object o=clazz.newInstance();
140             Map<String,List<String>>  r=(Map<String, List<String>>) clazz.getMethod("getDesciption").invoke(o);
141             List<String> classes=r.get("classes");
142             List<String> methods=r.get("methods");
143             
144             
145             //这里,根据获得的插件所提供的功能,来生成几个按钮显示,供我们调用
146             for(final String clas:classes){
147                 for(final String method:methods){
148                     Button btn=new Button(this);
149                     
150                     btn.setText(clas+" -> "+method+" 执行");
151                     
152                     
153                     //点击后,就执行插件所提供的方法
154                     btn.setOnClickListener(new OnClickListener() {
155                         
156                         @Override
157                         public void onClick(View v) {
158                             try {
159                                 Class c=context.getClassLoader().loadClass(plugins.get(e.curScreen).getPakageName()+"."+clas);
160                                 Object o1=c.newInstance();
161                                 
162                                 //这里注意,context实际上就是句柄,这里如果涉及到窗体,plugin的句柄其实是不行的,因为它没有可以
163 //依附的窗体
164                                 
165 //这个context是plugin的,通过测试,dialog这类行不通,Toast是可以的,因为
166 //Toast是依附于屏幕主窗口的
167 //c.getMethod(method,Context.class).invoke(o1,context); 
168                                                         
169 //这里则传递的是主程序的句柄
170                                 c.getMethod(method,Context.class).invoke(o1,AndoirdpluginActivity.this);
171                             
172                             } catch (Exception e) {
173                                 // TODO Auto-generated catch block
174                                 e.printStackTrace();
175                             } 
176                         }
177                     });
178                     llMainLayout.addView(btn);
179                 }
180             }
181             
182         
183         } catch (Exception e1) {
184             // TODO Auto-generated catch block
185             e1.printStackTrace();
186         }
187     
188     }
189 }

看注释吧,主要有两点

插件的扫描

这种方案是,每个插件以一个单独的apk发布,这样可以在程序中很灵活的知道是否有新的插件,提示用户下载安装,插件的apk清单描述为Action为非Luncher,Category为Default。

主程序侦听packgeManager的安装完成广播,之后扫描同包名(插件当然得这么定义了,只要通过packgeManager能判断是否为自己的插件就行)的apk,之后列出来,让用户选择是否加载。

插件的加载与调用

在获取包后,通过调用系统的api可以得到 sharedUserId 与主程序相同的apk的context,也就是句柄,获得了句柄,通过这个context可以得到classloader,之后就简单了,如何知道这个插件提供什么功能?

这个可以用xml描述,比如这个xml是插件apk的一个资源,就像spring这个框架一样。xml中描述了这个插件有哪些类,提供哪些方法,这些方 法需要传入什么参数,返回什么类型。我的demo中为了方便,是用接口,每个插件有一个类提供一个相同的方法,来获取一个map集合,获得这个插件的描 述。

ok,到这里就知道加载的插件提供什么功能了。

在上面贴出来的代码中,是循环遍历每个插件,并把每个插件提供的功能以Button的方式显示给用户,点击按钮,就执行了插件的功能,执行时,并不是 activity转向(这样就无意义了),而是在主程序自身的context句柄中执行,也就是在自身的窗体中执行。

代码中有一段注释,说明,如果插件有用到context时,记得传递进去的是主程序的context,这样窗体才能附加到这个句柄中,如果传递的是插件的context,它没有一个窗体实例,是无法将一些窗体附加进去的,无任何效果。

这里只提供思路,有时间的话研究一下,看能不能搞个通用的框架出来。还有另一种方法,不通过apk形式,以后会写出来。

这里有一些待验证的问题,比如插件的权限问题,如果插件需要的一些权限在主程序中没有声明,会是个什么情况,能不能实时申请呢?这个需要高人指点。或者在 主程序中把能声明的权限预先声明了也不错。还有就是native层代码的问题,如果插件包含了native层代码,会是个什么情况,这也需要验证。

这是demo下载:

http://files.cnblogs.com/hangxin1940/android_plugin_program.rar

原创,转载请注明  http://hangxin1940.cnblogs.com

[转载]为ASP.NET MVC创建一个基于Unity的ControllerFactory

mikel阅读(1153)

[转载]为ASP.NET MVC创建一个基于Unity的ControllerFactory – Artech – 博客园.

谈到IoC和ASP.NET的集成,很多人会先后想到Ninject,不过我们个人还是 倾向于Unity。这篇文章简单地介绍如果创建基于Unity的ControllerFactory。如下面的代码所示,我们通过直接继承 DefaultControllerFactory创建一个自定的UnityControllerFactory。构造函数指定的是配置的 UnityContainer的名称,如果没有显式指定则采用默认的UnityContainer。在重写的GetControllerInstance 方法中,直接调用IUnityContainer的Resolve方法根据Controller类型创建相应的对象。[源代码从这里下载]

   1: public class UnityControllerFactory: DefaultControllerFactory
   2: {
   3:     public IUnityContainer Container { get; private set; }
   4:     public UnityControllerFactory(string containerName = "")
   5:     {
   6:         IUnityContainer container = new UnityContainer();
   7:         UnityConfigurationSection configSection = ConfigurationManager.GetSection(UnityConfigurationSection.SectionName) as UnityConfigurationSection;
   8:         if (null == configSection && !string.IsNullOrEmpty(containerName))
   9:         {
  10:             throw new ConfigurationErrorsException("Cannot find <unity> configuration section");
  11:         }
  12:
  13:         if (string.IsNullOrEmpty(containerName))
  14:         {
  15:             configSection.Configure(container);
  16:         }
  17:         else
  18:         {
  19:             configSection.Configure(container, containerName);
  20:         }
  21:         this.Container = container;
  22:     }
  23:     protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
  24:     {
  25:         Guard.ArgumentNotNull(controllerType, "controllerType");
  26:         return (IController)this.Container.Resolve(controllerType);
  27:     }
  28: }

为了演示DefaultControllerFactory的作用,我们来创建一个简单的例子。假设我们要创建一个维护联系人的应用,我们通过具有 如下定义的Contact类型表示联系人,而IContactRepository接口定义了一个从存储中获取所有联系人的GetAllContacts 方法,DefaultContactRepository是对IContactRepository接口的实现。

   1: public class Contact
   2: {
   3:     public string Name { get; set; }
   4:     public string Gender { get; set; }
   5:     public string Address { get; set; }
   6: }
   7:
   8: public interface IContactRepository
   9: {
  10:     IEnumerable<Contact> GetAllContacts();
  11: }
  12:
  13: public class DefaultContactRepository : IContactRepository
  14: {
  15:     public IEnumerable<Contact> GetAllContacts()
  16:     {
  17:         yield return new Contact
  18:         {
  19:             Name = "Zhang San",
  20:             Gender = "Male",
  21:             Address = "#328, XingHu Street, Su Zhou, Jiang Su Province, PRC."
  22:         };
  23:
  24:         yield return new Contact
  25:         {
  26:             Name = "Li Si",
  27:             Gender = "Female",
  28:             Address = "#328, Jin Ji Hu Road, Su Zhou, Jiang Su Province, PRC."
  29:         };
  30:     }
  31: }

我们在Web应用的主页显示联系人列表,为此我创建了如下一个HomeController。在这里我们演示的是构造器注入,所以我们通过构造函数 指定的IContactRepository对象来初始化Repository属性。在Action方法Index中调用 IContactRepository的GetAllContacts方法为对应的View指定Model。

   1: public class HomeController : Controller
   2: {
   3:     public IContactRepository Repository { get; private set; }
   4:     public HomeController(IContactRepository repository)
   5:     {
   6:         this.Repository = repository;
   7:     }
   8:     public ActionResult Index()
   9:     {
  10:         return View(this.Repository.GetAllContacts());
  11:     }
  12: }

Index.cshtml代码如下所示,这是一个Model类型为IEnumerable<Contact>的View,它将所有的联系人信息列出来。

   1: @model IEnumerable<Artech.Web.Mvc.Extensions.Contact>
   2: @{
   3:     ViewBag.Title = "Index";
   4: }
   5:
   6: <h2>Contact List</h2>
   7:
   8: <div>
   9: <ul>
  10: @foreach (var contact in this.Model)
  11: {
  12:     <li>
  13:         <h3>@contact.Name</h3>
  14:         <p>Gender: @contact.Gender</p>
  15:         <p>Address: @contact.Address</p>
  16:         <hr />
  17:     </li>
  18: }
  19: </ul>
  20: </div>

自定义的UnityContainerFactory的注册定义在Gloable.asax中。初次之外,额外需要做的是忽略掉针对favicon.ico的路由,否则程序运行将会失败。

   1: public class MvcApplication : System.Web.HttpApplication
   2: {
   3:     public static void RegisterGlobalFilters(GlobalFilterCollection filters)
   4:     {
   5:         filters.Add(new HandleErrorAttribute());
   6:     }
   7:
   8:     public static void RegisterRoutes(RouteCollection routes)
   9:     {
  10:         routes.IgnoreRoute("favicon.ico");
  11:         routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
  12:         routes.MapRoute("Default", "{controller}/{action}/{id}",
  13:             new { controller = "Home", action = "Index", id = UrlParameter.Optional }
  14:         );
  15:     }
  16:
  17:     protected void Application_Start()
  18:     {
  19:         AreaRegistration.RegisterAllAreas();
  20:         RegisterGlobalFilters(GlobalFilters.Filters);
  21:         RegisterRoutes(RouteTable.Routes);
  22:
  23:         ControllerBuilder.Current.SetControllerFactory(new UnityControllerFactory());
  24:     }
  25: }

接口IContactRepository和DefualtContactRepository之间的映射关系定义在如下所示的<unity>配置中。

   1: <unity>
   2:   <alias alias="IContactRepository" type="Artech.Web.Mvc.Extensions.IContactRepository, UnityIntegration" />
   3:   <alias alias="DefaultContactRepository" type="Artech.Web.Mvc.Extensions.DefaultContactRepository, UnityIntegration" />
   4:   <container>
   5:     <register type="IContactRepository" mapTo="DefaultContactRepository"/>
   6:   </container>
   7: </unity>

通过浏览器访问Web应用的主页,将会得到如下所示的联系人列表。

image