[转载]Java:设计 REST 风格的 MVC 框架

mikel阅读(934)

[转载]设计 REST 风格的 MVC 框架.

传统的 JavaEE MVC 框架如 Struts 等都是基于 Action 设计的后缀式映射,然而,流行的 Web 趋势是 REST 风格的架构。尽管使用 Filter 或者 Apache mod_rewrite 能够通过 URL 重写实现 REST 风格的 URL,为什么不直接设计一个全新的 REST 风格的 MVC 框架呢? 本文将讲述如何从头设计一个基于 REST 风格的 Java MVC 框架,配合 Annotation,最大限度地简化 Web 应用的开发,您甚至编写一行代码就可以实现“Hello, world”。

Java 开发者对 MVC 框架一定不陌生,从 Struts 到 WebWork,Java MVC 框架层出不穷。我们已经习惯了处理 *.do 或 *.action 风格的 URL,为每一个 URL 编写一个控制器,并继承一个 Action 或者 Controller 接口。然而,流行的 Web 趋势是使用更加简单,对用户和搜索引擎更加友好的 REST 风格的 URL。例如,来自豆瓣的一本书的链接是 http://www.douban.com/subject/2129650/, 而非 http://www.douban.com/subject.do?id=2129650

有经验的 Java Web 开发人员会使用 URL 重写的方式来实现类似的 URL,例如,为前端 Apache 服务器配置 mod_rewrite 模块,并依次为每个需要实现 URL 重写的地址编写负责转换的正则表达式,或者,通过一个自定义的 RewriteFilter,使用 Java Web 服务器提供的 Filter 和请求转发(Forward)功能实现 URL 重写,不过,仍需要为每个地址编写正则表达式。

既然 URL 重写如此繁琐,为何不直接设计一个原生支持 REST 风格的 MVC 框架呢?

要设计并实现这样一个 MVC 框架并不困难,下面,我们从零开始,仔细研究如何实现 REST 风格的 URL 映射,并与常见的 IoC 容器如 Spring 框架集成。这个全新的 MVC 框架暂命名为 WebWind。

术语

MVC:Model-View-Controller,是一种常见的 UI 架构模式,通过分离 Model(模型)、View(视图)和 Controller(控制器),可以更容易实现易于扩展的 UI。在 Web 应用程序中,Model 指后台返回的数据;View 指需要渲染的页面,通常是 JSP 或者其他模板页面,渲染后的结果通常是 HTML;Controller 指 Web 开发人员编写的处理不同 URL 的控制器(在 Struts 中被称之为 Action),而 MVC 框架本身还有一个前置控制器,用于接收所有的 URL 请求,并根据 URL 地址分发到 Web 开发人员编写的 Controller 中。

IoC:Invertion-of-Control,控制反转,是目前流行的管理所有组件生命周期和复杂依赖关系的容器,例如 Spring 容器。

Template:模板,通过渲染,模板中的变量将被 Model 的实际数据所替换,然后,生成的内容即是用户在浏览器中看到的 HTML。模板也能实现判断、循环等简单逻辑。本质上,JSP 页面也是一种模板。此外,还有许多第三方模板引擎,如 Velocity,FreeMarker 等。


回页首

设计目标

和传统的 Struts 等 MVC 框架完全不同,为了支持 REST 风格的 URL,我们并不把一个 URL 映射到一个 Controller 类(或者 Struts 的 Action),而是直接把一个 URL 映射到一个方法,这样,Web 开发人员就可以将多个功能类似的方法放到一个 Controller 中,并且,Controller 没有强制要求必须实现某个接口。一个 Controller 通常拥有多个方法,每个方法负责处理一个 URL。例如,一个管理 Blog 的 Controller 定义起来就像清单 1 所示。
清单 1. 管理 Blog 的 Controller 定义

				
public class Blog { 
    @Mapping("/create/$1") 
    Public void create(int userId) { ... } 

    @Mapping("/display/$1/$2") 
    Public void display(int userId, int postId) { ... } 

    @Mapping("/edit/$1/$2") 
    Public void edit(int userId, int postId) { ... } 

    @Mapping("/delete/$1/$2") 
    Public String delete(int userId, int postId) { ... } 
} 

@Mapping() 注解指示了这是一个处理 URL 映射的方法,URL 中的参数 $1、$2 ……则将作为方法参数传入。对于一个“/blog/1234/5678”的 URL,对应的方法将自动获得参数 userId=1234 和 postId=5678。同时,也无需任何与 URL 映射相关的 XML 配置文件。

使用 $1、$2 ……来定义 URL 中的可变参数要比正则表达式更简单,我们需要在 MVC 框架内部将其转化为正则表达式,以便匹配 URL。

此外,对于方法返回值,也未作强制要求。


回页首

集成 IoC

当接收到来自浏览器的请求,并匹配到合适的 URL 时,应该转发给某个 Controller 实例的某个标记有 @Mapping 的方法,这需要持有所有 Controller 的实例。不过,让一个 MVC 框架去管理这些组件并不是一个好的设计,这些组件可以很容易地被 IoC 容器管理,MVC 框架需要做的仅仅是向 IoC 容器请求并获取这些组件的实例。

为了解耦一种特定的 IoC 容器,我们通过 ContainerFactory 来获取所有 Controller 组件的实例,如清单 2 所示。
清单 2. 定义 ContainerFactory

				
public interface ContainerFactory { 

    void init(Config config); 

    List<Object> findAllBeans(); 

    void destroy(); 
} 

其中,关键方法 findAllBeans() 返回 IoC 容器管理的所有 Bean,然后,扫描每一个 Bean 的所有 public 方法,并引用那些标记有 @Mapping 的方法实例。

我们设计目标是支持 Spring 和 Guice 这两种容器,对于 Spring 容器,可以通过 ApplicationContext 获得所有的 Bean 引用,代码见清单 3。
清单 3. 定义 SpringContainerFactory

				
public class SpringContainerFactory implements ContainerFactory { 
    private ApplicationContext appContext; 

    public List<Object> findAllBeans() { 
        String[] beanNames = appContext.getBeanDefinitionNames(); 
        List<Object> beans = new ArrayList<Object>(beanNames.length); 
        for (int i=0; i<beanNames.length; i++) { 
            beans.add(appContext.getBean(beanNames[i])); 
        } 
        return beans; 
    } 
    ... 
} 

对于 Guice 容器,通过 Injector 实例可以返回所有绑定对象的实例,代码见清单 4。
清单 4. 定义 GuiceContainerFactory

				
public class GuiceContainerFactory implements ContainerFactory { 
    private Injector injector; 

    public List<Object> findAllBeans() { 
        Map<Key<?>, Binding<?>> map = injector.getBindings(); 
        Set<Key<?>> keys = map.keySet(); 
        List<Object> list = new ArrayList<Object>(keys.size()); 
        for (Key<?> key : keys) { 
            Object bean = injector.getInstance(key); 
            list.add(bean); 
        } 
        return list; 
    } 
    ... 
} 

类似的,通过扩展 ContainerFactory,就可以支持更多的 IoC 容器,如 PicoContainer。

出于效率的考虑,我们缓存所有来自 IoC 的 Controller 实例,无论其在 IoC 中配置为 Singleton 还是 Prototype 类型。当然,也可以修改代码,每次都从 IoC 容器中重新请求实例。


回页首

设计请求转发

和 Struts 等常见 MVC 框架一样,我们也需要实现一个前置控制器,通常命名为 DispatcherServlet,用于接收所有的请求,并作出合适的转发。在 Servlet 规范中,有以下几种常见的 URL 匹配模式:

  • /abc:精确匹配,通常用于映射自定义的 Servlet;
  • *.do:后缀模式匹配,常见的 MVC 框架都采用这种模式;
  • /app/*:前缀模式匹配,这要求 URL 必须以固定前缀开头;
  • /:匹配默认的 Servlet,当一个 URL 没有匹配到任何 Servlet 时,就匹配默认的 Servlet。一个 Web 应用程序如果没有映射默认的 Servlet,Web 服务器会自动为 Web 应用程序添加一个默认的 Servlet。

REST 风格的 URL 一般不含后缀,我们只能将 DispatcherServlet 映射到“/”,使之变为一个默认的 Servlet,这样,就可以对任意的 URL 进行处理。

由于无法像 Struts 等传统的 MVC 框架根据后缀直接将一个 URL 映射到一个 Controller,我们必须依次匹配每个有能力处理 HTTP 请求的 @Mapping 方法。完整的 HTTP 请求处理流程如图 1 所示。
图 1. 请求处理流程
图 1. 请求处理流程

当扫描到标记有 @Mapping 注解的方法时,需要首先检查 URL 与方法参数是否匹配,UrlMatcher 用于将 @Mapping 中包含 $1、$2 ……的字符串变为正则表达式,进行预编译,并检查参数个数是否符合方法参数,代码见清单 5。
清单 5. 定义 UrlMatcher

				
final class UrlMatcher { 
    final String url; 
    int[] orders; 
    Pattern pattern; 

    public UrlMatcher(String url) { 
        ... 
    } 
} 

将 @Mapping 中包含 $1、$2 ……的字符串变为正则表达式的转换规则是,依次将每个 $n 替换为 ([^\\/]*),其余部分作精确匹配。例如,“/blog/$1/$2”变化后的正则表达式为:

 ^\\/blog\\/([^\\/]*)\\/([^\\/]*)$ 

请注意,Java 字符串需要两个连续的“\\”表示正则表达式中的转义字符“\”。将“/”排除在变量匹配之外可以避免很多歧义。

调用一个实例方法则由 Action 类表示,它持有类实例、方法引用和方法参数类型,代码见清单 6。
清单 6. 定义 Action

				
class Action { 
    public final Object instance; 
    public final Method method; 
    public final Class<?>[] arguments; 

    public Action(Object instance, Method method) { 
        this.instance = instance; 
        this.method = method; 
        this.arguments = method.getParameterTypes(); 
    } 
} 

负责请求转发的 Dispatcher 通过关联 UrlMatcher 与 Action,就可以匹配到合适的 URL,并转发给相应的 Action,代码见清单 7。
清单 7. 定义 Dispatcher

				
class Dispatcher  { 
    private UrlMatcher[] urlMatchers; 
    private Map<UrlMatcher, Action> urlMap = new HashMap<UrlMatcher, Action>(); 
    .... 
} 

当 Dispatcher 接收到一个 URL 请求时,遍历所有的 UrlMatcher,找到第一个匹配 URL 的 UrlMatcher,并从 URL 中提取方法参数,代码见清单 8。
清单 8. 匹配并从 URL 中提取参数

				
final class UrlMatcher { 
    ... 

    /** 
     * 根据正则表达式匹配 URL,若匹配成功,返回从 URL 中提取的参数,
     * 若匹配失败,返回 null 
     */ 
    public String[] getMatchedParameters(String url) { 
        Matcher m = pattern.matcher(url); 
        if (!m.matches()) 
            return null; 
        if (orders.length==0) 
            return EMPTY_STRINGS; 
        String[] params = new String[orders.length]; 
        for (int i=0; i<orders.length; i++) { 
            params[orders[i]] = m.group(i+1); 
        } 
        return params; 
    } 
} 

根据 URL 找到匹配的 Action 后,就可以构造一个 Execution 对象,并根据方法签名将 URL 中的 String 转换为合适的方法参数类型,准备好全部参数,代码见清单 9。
清单 9. 构造 Exectuion

				
class Execution { 
    public final HttpServletRequest request; 
    public final HttpServletResponse response; 
    private final Action action; 
    private final Object[] args; 
    ... 

    public Object execute() throws Exception { 
        try { 
            return action.method.invoke(action.instance, args); 
        } 
        catch (InvocationTargetException e) { 
            Throwable t = e.getCause(); 
            if (t!=null && t instanceof Exception) 
                throw (Exception) t; 
            throw e; 
        } 
    } 
} 

调用 execute() 方法就可以执行目标方法,并返回一个结果。请注意,当通过反射调用方法失败时,我们通过查找 InvocationTargetException 的根异常并将其抛出,这样,客户端就能捕获正确的原始异常。

为了最大限度地增加灵活性,我们并不强制要求 URL 的处理方法返回某一种类型。我们设计支持以下返回值:

  • String:当返回一个 String 时,自动将其作为 HTML 写入 HttpServletResponse;
  • void:当返回 void 时,不做任何操作;
  • Renderer:当返回 Renderer 对象时,将调用 Renderer 对象的 render 方法渲染 HTML 页面。

最后需要考虑的是,由于我们将 DispatcherServlet 映射为“/”,即默认的 Servlet,则所有的未匹配成功的 URL 都将由 DispatcherServlet 处理,包括所有静态文件,因此,当未匹配到任何 Controller 的 @Mapping 方法后,DispatcherServlet 将试图按 URL 查找对应的静态文件,我们用 StaticFileHandler 封装,主要代码见清单 10。
清单 10. 处理静态文件

				
class StaticFileHandler { 
    ... 
    public void handle(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException { 
        String url = request.getRequestURI(); 
        String path = request.getServletPath(); 
        url = url.substring(path.length()); 
        if (url.toUpperCase().startsWith("/WEB-INF/")) { 
            response.sendError(HttpServletResponse.SC_NOT_FOUND); 
            return; 
        } 
        int n = url.indexOf('?'); 
        if (n!=(-1)) 
            url = url.substring(0, n); 
        n = url.indexOf('#'); 
        if (n!=(-1)) 
            url = url.substring(0, n); 
        File f = new File(servletContext.getRealPath(url)); 
        if (! f.isFile()) { 
            response.sendError(HttpServletResponse.SC_NOT_FOUND); 
            return; 
        } 
        long ifModifiedSince = request.getDateHeader("If-Modified-Since"); 
        long lastModified = f.lastModified(); 
        if (ifModifiedSince!=(-1) && ifModifiedSince>=lastModified) { 
            response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); 
            return; 
        } 
        response.setDateHeader("Last-Modified", lastModified); 
        response.setContentLength((int)f.length()); 
        response.setContentType(getMimeType(f)); 
        sendFile(f, response.getOutputStream()); 
    } 
} 

处理静态文件时要过滤 /WEB-INF/ 目录,否则将造成安全漏洞。


回页首

集成模板引擎

作为示例,返回一个“<h1>Hello, world!</h1>”作为 HTML 页面非常容易。然而,实际应用的页面通常是极其复杂的,需要一个模板引擎来渲染出 HTML。可以把 JSP 看作是一种模板,只要不在 JSP 页面中编写复杂的 Java 代码。我们的设计目标是实现对 JSP 和 Velocity 这两种模板的支持。

和集成 IoC 框架类似,我们需要解耦 MVC 与模板系统,因此,TemplateFactory 用于初始化模板引擎,并返回 Template 模板对象。TemplateFactory 定义见清单 11。
清单 11. 定义 TemplateFactory

				
public abstract class TemplateFactory { 
    private static TemplateFactory instance; 
    public static TemplateFactory getTemplateFactory() { 
        return instance; 
    } 

    public abstract Template loadTemplate(String path) throws Exception; 
} 

Template 接口则实现真正的渲染任务。定义见清单 12。
清单 12. 定义 Template

				
public interface Template { 
    void render(HttpServletRequest request, HttpServletResponse response, 
        Map<String, Object> model) throws Exception; 
} 

以 JSP 为例,实现 JspTemplateFactory 非常容易。代码见清单 13。
清单 13. 定义 JspTemplateFactory

				
public class JspTemplateFactory extends TemplateFactory { 
    private Log log = LogFactory.getLog(getClass()); 

    public Template loadTemplate(String path) throws Exception { 
        if (log.isDebugEnabled()) 
            log.debug("Load JSP template '" + path + "'."); 
        return new JspTemplate(path); 
    } 

    public void init(Config config) { 
        log.info("JspTemplateFactory init ok."); 
    } 
} 

JspTemplate 用于渲染页面,只需要传入 JSP 的路径,将 Model 绑定到 HttpServletRequest,就可以调用 Servlet 规范的 forward 方法将请求转发给指定的 JSP 页面并渲染。代码见清单 14。
清单 14. 定义 JspTemplate

				
public class JspTemplate implements Template { 
    private String path; 

    public JspTemplate(String path) { 
        this.path = path; 
    } 

    public void render(HttpServletRequest request, HttpServletResponse response, 
            Map<String, Object> model) throws Exception { 
        Set<String> keys = model.keySet(); 
        for (String key : keys) { 
            request.setAttribute(key, model.get(key)); 
        } 
        request.getRequestDispatcher(path).forward(request, response); 
    } 
} 

另一种比 JSP 更加简单且灵活的模板引擎是 Velocity,它使用更简洁的语法来渲染页面,对页面设计人员更加友好,并且完全阻止了开发人员试图在页面中编写 Java 代码的可能性。使用 Velocity 编写的页面示例如清单 15 所示。
清单 15. Velocity 模板页面

				
<html> 
    <head><title>${title}</title></head> 
    <body><h1>Hello, ${name}!</body> 
</html> 

通过 VelocityTemplateFactory 和 VelocityTemplate 就可以实现对 Velocity 的集成。不过,从 Web 开发人员看来,并不需要知道具体使用的模板,客户端仅需要提供模板路径和一个由 Map<String, Object> 组成的 Model,然后返回一个 TemplateRenderer 对象。代码如清单 16 所示。
清单 16. 定义 TemplateRenderer

				
public class TemplateRenderer extends Renderer { 
    private String path; 
    private Map<String, Object> model; 

    public TemplateRenderer(String path, Map<String, Object> model) { 
        this.path = path; 
        this.model = model; 
    } 

    @Override 
    public void render(ServletContext context, HttpServletRequest request, 
            HttpServletResponse response) throws Exception { 
        TemplateFactory.getTemplateFactory() 
                .loadTemplate(path) 
                .render(request, response, model); 
    } 
} 

TemplateRenderer 通过简单地调用 render 方法就实现了页面渲染。为了指定 Jsp 或 Velocity,需要在 web.xml 中配置 DispatcherServlet 的初始参数。配置示例请参考清单 17。
清单 17. 配置 Velocity 作为模板引擎

				
<servlet> 
    <servlet-name>dispatcher</servlet-name> 
    <servlet-class>org.expressme.webwind.DispatcherServlet</servlet-class> 
    <init-param> 
        <param-name>template</param-name> 
        <param-value>Velocity</param-value> 
    </init-param> 
</servlet> 

如果没有该缺省参数,那就使用默认的 Jsp。

类似的,通过扩展 TemplateFactory 和 Template,就可以添加更多的模板支持,例如 FreeMarker。


回页首

设计拦截器

拦截器和 Servlet 规范中的 Filter 非常类似,不过 Filter 的作用范围是整个 HttpServletRequest 的处理过程,而拦截器仅作用于 Controller,不涉及到 View 的渲染,在大多数情况下,使用拦截器比 Filter 速度要快,尤其是绑定数据库事务时,拦截器能缩短数据库事务开启的时间。

拦截器接口 Interceptor 定义如清单 18 所示。
清单 18. 定义 Interceptor

				
public interface Interceptor { 
    void intercept(Execution execution, InterceptorChain chain) throws Exception; 
} 

和 Filter 类似,InterceptorChain 代表拦截器链。InterceptorChain 定义如清单 19 所示。
清单 19. 定义 InterceptorChain

				
public interface InterceptorChain { 
    void doInterceptor(Execution execution) throws Exception; 
} 

实现 InterceptorChain 要比实现 FilterChain 简单,因为 Filter 需要处理 Request、Forward、Include 和 Error 这 4 种请求转发的情况,而 Interceptor 仅拦截 Request。当 MVC 框架处理一个请求时,先初始化一个拦截器链,然后,依次调用链上的每个拦截器。请参考清单 20 所示的代码。
清单 20. 实现 InterceptorChain 接口

				
class InterceptorChainImpl implements InterceptorChain { 
    private final Interceptor[] interceptors; 
    private int index = 0; 
    private Object result = null; 

    InterceptorChainImpl(Interceptor[] interceptors) { 
        this.interceptors = interceptors; 
    } 

    Object getResult() { 
        return result; 
    } 

    public void doInterceptor(Execution execution) throws Exception { 
        if(index==interceptors.length) 
            result = execution.execute(); 
        else { 
            // must update index first, otherwise will cause stack overflow: 
            index++; 
            interceptors[index-1].intercept(execution, this); 
        } 
    } 
} 

成员变量 index 表示当前链上的第 N 个拦截器,当最后一个拦截器被调用后,InterceptorChain 才真正调用 Execution 对象的 execute() 方法,并保存其返回结果,整个请求处理过程结束,进入渲染阶段。清单 21 演示了如何调用拦截器链的代码。
清单 21. 调用拦截器链

				
class Dispatcher  { 
    ... 
    private Interceptor[] interceptors; 
    void handleExecution(Execution execution, HttpServletRequest request, 
        HttpServletResponse response) throws ServletException, IOException { 
        InterceptorChainImpl chains = new InterceptorChainImpl(interceptors); 
        chains.doInterceptor(execution); 
        handleResult(request, response, chains.getResult()); 
    } 
} 

当 Controller 方法被调用完毕后,handleResult() 方法用于处理执行结果。


回页首

渲染

由于我们没有强制 HTTP 处理方法的返回类型,因此,handleResult() 方法针对不同的返回值将做不同的处理。代码如清单 22 所示。
清单 22. 处理返回值

				
class Dispatcher  { 
    ... 
    void handleResult(HttpServletRequest request, HttpServletResponse response, 
            Object result) throws Exception { 
        if (result==null) 
            return; 
        if (result instanceof Renderer) { 
            Renderer r = (Renderer) result; 
            r.render(this.servletContext, request, response); 
            return; 
        } 
        if (result instanceof String) { 
            String s = (String) result; 
            if (s.startsWith("redirect:")) { 
                response.sendRedirect(s.substring(9)); 
                return; 
            } 
            new TextRenderer(s).render(servletContext, request, response); 
            return; 
        } 
        throw new ServletException("Cannot handle result with type '"
                + result.getClass().getName() + "'."); 
    } 
} 

如果返回 null,则认为 HTTP 请求已处理完成,不做任何处理;如果返回 Renderer,则调用 Renderer 对象的 render() 方法渲染视图;如果返回 String,则根据前缀是否有“redirect:”判断是重定向还是作为 HTML 返回给浏览器。这样,客户端可以不必访问 HttpServletResponse 对象就可以非常方便地实现重定向。代码如清单 23 所示。
清单 23. 重定向

				
@Mapping("/register") 
String register() { 
    ... 
    if (success) 
        return "redirect:/reg/success"; 
    return "redirect:/reg/failed"; 
} 

扩展 Renderer 还可以处理更多的格式,例如,向浏览器返回 JavaScript 代码等。


回页首

扩展

使用 Filter 转发

对于请求转发,除了使用 DispatcherServlet 外,还可以使用 Filter 来拦截所有请求,并直接在 Filter 内实现请求转发和处理。使用 Filter 的一个好处是如果 URL 没有被任何 Controller 的映射方法匹配到,则可以简单地调用 FilterChain.doFilter() 将 HTTP 请求传递给下一个 Filter,这样,我们就不必自己处理静态文件,而由 Web 服务器提供的默认 Servlet 处理,效率更高。和 DispatcherServlet 类似,我们编写一个 DispatcherFilter 作为前置处理器,负责转发请求,代码见清单 24。
清单 24. 定义 DispatcherFilter

				
public class DispatcherFilter implements Filter { 
    ... 
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) 
    throws IOException, ServletException { 
        HttpServletRequest httpReq = (HttpServletRequest) req; 
        HttpServletResponse httpResp = (HttpServletResponse) resp; 
        String method = httpReq.getMethod(); 
        if ("GET".equals(method) || "POST".equals(method)) { 
            if (!dispatcher.service(httpReq, httpResp)) 
                chain.doFilter(req, resp); 
            return; 
        } 
        httpResp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); 
    } 
} 

如果用 DispatcherFilter 代替 DispatcherServlet,则我们需要过滤“/*”,在 web.xml 中添加声明如清单 25 所示。
清单 25. 声明 DispatcherFilter

				
<filter> 
    <filter-name>dispatcher</servlet-name> 
    <filter-class>org.expressme.webwind.DispatcherFilter</servlet-class> 
</filter> 
<filter-mapping> 
    <filter-name>dispatcher</servlet-name> 
    <url-pattern>/*</url-pattern> 
</filter-mapping> 

访问 Request 和 Response 对象

如何在 @Mapping 方法中访问 Servlet 对象?如 HttpServletRequest,HttpServletResponse,HttpSession 和 ServletContext。ThreadLocal 是一个最简单有效的解决方案。我们编写一个 ActionContext,通过 ThreadLocal 来封装对 Request 等对象的访问。代码见清单 26。
清单 26. 定义 ActionContext

				
public final class ActionContext { 
    private static final ThreadLocal<ActionContext> actionContextThreadLocal 
            = new ThreadLocal<ActionContext>(); 

    private ServletContext context; 
    private HttpServletRequest request; 
    private HttpServletResponse response; 

    public ServletContext getServletContext() { 
        return context; 
    } 

    public HttpServletRequest getHttpServletRequest() { 
        return request; 
    } 

    public HttpServletResponse getHttpServletResponse() { 
        return response; 
    } 

    public HttpSession getHttpSession() { 
        return request.getSession(); 
    } 

    public static ActionContext getActionContext() { 
        return actionContextThreadLocal.get(); 
    } 

    static void setActionContext(ServletContext context, 
            HttpServletRequest request, HttpServletResponse response) { 
        ActionContext ctx = new ActionContext(); 
        ctx.context = context; 
        ctx.request = request; 
        ctx.response = response; 
        actionContextThreadLocal.set(ctx); 
    } 

    static void removeActionContext() { 
        actionContextThreadLocal.remove(); 
    } 
} 

在 Dispatcher 的 handleExecution() 方法中,初始化 ActionContext,并在 finally 中移除所有已绑定变量,代码见清单 27。
清单 27. 初始化 ActionContext

				
class Dispatcher { 
    ... 
    void handleExecution(Execution execution, HttpServletRequest request, 
    HttpServletResponse response) throws ServletException, IOException { 
        ActionContext.setActionContext(servletContext, request, response); 
        try { 
            InterceptorChainImpl chains = new InterceptorChainImpl(interceptors); 
            chains.doInterceptor(execution); 
            handleResult(request, response, chains.getResult()); 
        } 
        catch (Exception e) { 
            handleException(request, response, e); 
        } 
        finally { 
            ActionContext.removeActionContext(); 
        } 
    } 
} 

这样,在 @Mapping 方法内部,可以随时获得需要的 Request、Response、 Session 和 ServletContext 对象。

处理文件上传

Servlet API 本身并没有提供对文件上传的支持,要处理文件上传,我们需要使用 Commons FileUpload 之类的第三方扩展包。考虑到 Commons FileUpload 是使用最广泛的文件上传包,我们希望能集成 Commons FileUpload,但是,不要暴露 Commons FileUpload 的任何 API 给 MVC 的客户端,客户端应该可以直接从一个普通的 HttpServletRequest 对象中获取上传文件。

要让 MVC 客户端直接使用 HttpServletRequest,我们可以用自定义的 MultipartHttpServletRequest 替换原始的 HttpServletRequest,这样,客户端代码可以通过 instanceof 判断是否是一个 Multipart 格式的 Request,如果是,就强制转型为 MultipartHttpServletRequest,然后,获取上传的文件流。

核心思想是从 HttpServletRequestWrapper 派生 MultipartHttpServletRequest,这样,MultipartHttpServletRequest 具有 HttpServletRequest 接口。MultipartHttpServletRequest 的定义如清单 28 所示。
清单 28. 定义 MultipartHttpServletRequest

				
public class MultipartHttpServletRequest extends HttpServletRequestWrapper { 
    final HttpServletRequest target; 
    final Map<String, List<FileItemStream>> fileItems; 
    final Map<String, List<String>> formItems; 

    public MultipartHttpServletRequest(HttpServletRequest request, long maxFileSize) 
    throws IOException { 
        super(request); 
        this.target = request; 
        this.fileItems = new HashMap<String, List<FileItemStream>>(); 
        this.formItems = new HashMap<String, List<String>>(); 
        ServletFileUpload upload = new ServletFileUpload(); 
        upload.setFileSizeMax(maxFileSize); 
        try { 

...解析Multipart ...

        } 
        catch (FileUploadException e) { 
            throw new IOException(e); 
        } 
    } 

    public InputStream getFileInputStream(String fieldName) throws IOException { 
        List<FileItemStream> list = fileItems.get(fieldName); 
        if (list==null) 
            throw new IOException("No file item with name '" + fieldName + "'."); 
        return list.get(0).openStream(); 
    }; 
} 

对于正常的 Field 参数,保存在成员变量 Map<String, List<String>> formItems 中,通过覆写 getParameter()、getParameters() 等方法,就可以让客户端把 MultipartHttpServletRequest 也当作一个普通的 Request 来操作,代码见清单 29。
清单 29. 覆写 getParameter

				
public class MultipartHttpServletRequest extends HttpServletRequestWrapper { 
    ... 
    @Override 
    public String getParameter(String name) { 
        List<String> list = formItems.get(name); 
        if (list==null) 
            return null; 
        return list.get(0); 
    } 

    @Override 
    @SuppressWarnings("unchecked") 
    public Map getParameterMap() { 
        Map<String, String[]> map = new HashMap<String, String[]>(); 
        Set<String> keys = formItems.keySet(); 
        for (String key : keys) { 
            List<String> list = formItems.get(key); 
            map.put(key, list.toArray(new String[list.size()])); 
        } 
        return Collections.unmodifiableMap(map); 
    } 

    @Override 
    @SuppressWarnings("unchecked") 
    public Enumeration getParameterNames() { 
        return Collections.enumeration(formItems.keySet()); 
    } 

    @Override 
    public String[] getParameterValues(String name) { 
        List<String> list = formItems.get(name); 
        if (list==null) 
            return null; 
        return list.toArray(new String[list.size()]); 
    } 
} 

为了简化配置,在 Web 应用程序启动的时候,自动检测当前 ClassPath 下是否有 Commons FileUpload,如果存在,文件上传功能就自动开启,如果不存在,文件上传功能就不可用,这样,客户端只需要简单地把 Commons FileUpload 的 jar 包放入 /WEB-INF/lib/,不需任何配置就可以直接使用。核心代码见清单 30。
清单 30. 检测 Commons FileUpload

				
class Dispatcher { 
    private boolean multipartSupport = false; 
    ... 
    void initAll(Config config) throws Exception { 
        try { 
            Class.forName("org.apache.commons.fileupload.servlet.ServletFileUpload"); 
            this.multipartSupport = true; 
        } 
        catch (ClassNotFoundException e) { 
            log.info("CommonsFileUpload not found."); 
        } 
        ... 
    } 

    void handleExecution(Execution execution, HttpServletRequest request, 
            HttpServletResponse response) throws ServletException, IOException { 
        if (this.multipartSupport) { 
            if (MultipartHttpServletRequest.isMultipartRequest(request)) { 
                request = new MultipartHttpServletRequest(request, maxFileSize); 
            } 
        } 
        ... 
    } 
    ... 
} 

回页首

小结

要从头设计并实现一个 MVC 框架其实并不困难,设计 WebWind 的目标是改善 Web 应用程序的 URL 结构,并通过自动提取和映射 URL 中的参数,简化控制器的编写。WebWind 适合那些从头构造的新的互联网应用,以便天生支持 REST 风格的 URL。但是,它不适合改造已有的企业应用程序,企业应用的页面不需要搜索引擎的索引,其用户对 URL 地址的友好程度通常也并不关心。

参考资料

学 习

[转载]ASP.NET MVC 2.0 学习笔记 和 Demo共享

mikel阅读(1238)

[转载]MVC 2.0 学习笔记 和 Demo共享 – Teracy ‘s space—> – 博客园.

好久没有写BLOG了,可能没有技术上的新发现,也不知道在这里和大家共享什么。 最近在研究MVC 2.0, 这个玩意大家都知道是把软件系统分为三个基本部分:模型(Model),视图(View)和控制器(Controller)。说它好,它确实好,开发起来 简单方便;说它不好,也确实存在一些问题。比如说版本升级的风险,性能比较研究现在都不够深刻。我刚刚开始学的时候在网上找资料找了好多都是一样的,并且 现在大部分都是针对MVC1.0的介绍。为了让初学者少走弯路,就借此地和大家分享下我的学习经历,我也不卖官子写一讲,二讲……了,我觉得这个 入门了就走出了一大步,所以就一次性全部拿出来说啦。

要用这个第一步:要装一个:AspNetMVC2_VS2008.exe ,这个东西网上一搜很多地方由下载,看你的VS是什么版本的,下载安装好了就会在你的VS新建模板的时候有:;新建一个 “ASP.NET MVC 2 Web Application”,然后跟着会弹出问你是否创建一个测试的项目,我们选择否。然后系统就会自动创建一些问题,这些文件就是我们学习的模板,我们不 懂原理不要紧,照葫芦画瓢,画几个就懂了。项目建好了,我们要说说里面的文件的使用。我们的 View文件中有几个原始目录,,这个要搞清楚的就是controller是文件夹,action就是页面。 如我们要浏览Home目录下的Index.aspx;就要在根目录下的 Global.asax 里面设置:

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
);

}

知道怎么浏览了,我们下一步要看怎么Add View,Add View分好几种选项,空文件和强类型数据文件,初学最好用空文件,因为强类型的还要改一些东西。我们新建一个文件夹D1,然后在D1下建 V1.aspx;然后像上面一样浏览是浏览不出来的,因为我们还要在Controllers文件夹目录下的类文件中加上相应的控制。

比如: public ActionResult V1()
{
return View();
}

这样的。否则浏览的时候就会提示:“ /应用程序中的服务器错误。无法找到资源。” 。

简单的Add View 好了,我们就看看里面怎么用,一开始看看传值方式:

ViewData[“myDate”] = “测试用ViewDate简单的传值,仅限于当前页面“;
TempData[“myTempDate”] = “测试用TempDate简单的传值,可以作为页面跳转间传值使用,但只能用一次,第二次请求的时候就没有了“;

ViewDate是很强悍的玩意,什么东西都能存,string,List,DataTable,某个类的实体都没有问题就想Session一样。 我的Demo中都有实例的。

传值还有一部分就是链接传值,比如我们在V4中创建一个链接:
<%= Html.ActionLink(“测试链接传到V5”, “V5”, “D1″,
new { word = ” 第一个值word “,key=” 第二个值:key “,uid=” 第三个值:uid ” }, new { @class=”D1″}
)%>

这样就是创建了一个链接,第一个参数是“链接文字”,第二个参数是:“链接需要跳转的页面”,第三个参数是“文件夹”,第四个参数是:“链接的参 数”;

V5页面接受上面这个链接的方法有两种:

/// <summary>
/// 方法一
/// </summary>
/// <param name=”word”></param>
/// <returns></returns>
public ActionResult V5(string word, string key, string uid)  // 这里定义的变量名字一定要和链接上带的名字一摸一样的。
{
ViewData[“qt”] = word + key + uid;

return View();
}
/// <summary>
/// 方法二
/// </summary>
/// <returns></returns>
public ActionResult V5()
{
ViewData[“qt”] = Request.QueryString[“word”].ToString() + Request.QueryString[“key”].ToString() + Request.QueryString[“uid”].ToString();

return View();
}

这是我们经常用的链接传值。

基本入门知道这些就OK了,再细就要靠自己去挖掘了。Demo中还有关于表单提交,注册,登陆和上面说的Add View选择强类型的方法,大家可以自己研究研究。

Demo地址: http://files.cnblogs.com/teracy/Demo100528.zip

[转载]浅谈增强现实(Augmented Reality)

mikel阅读(1599)

[转载]浅谈增强现实(Augmented Reality) – @luweii – 博客园.

互联网的下一个热潮

随着互联网的不断发展,各种需求不断的被发现和改善,Yahoo、Google、Youtube,Facebook、Twitter等的出现掀起一 次又一次的热潮。那么下一个热潮将会是什么呢?
个人认为,下一个热潮很有可能将会是,在这里想给大家介绍的,最近在世界各地备受关注的增强现实 (Augmented Reality,以下简称AR)技术。
首先AR是什么呢?
关于AR有很多种定义方法,我自己的理解是:AR是在人 们接触到的真实世界上,叠加虚拟电子信息,对真实世界的信息进行增强或者扩张,帮助利用者们从事各种活动。

为什么说AR将会是下一个热潮

现在的互联网,信息量非常的巨大,然而人们对这些信息的利用过度依赖于搜索,需要输入合适的关键词才能找到自己想要的信息。除了搜索之外,有没有更 好的方式呢,能够让人们更加便利,随时随地的利用互联网上的信息呢?
人与人之间的沟通交流,现实世界过于生硬,虚拟世界过于遥远,要真正找到志同 道合的知己很难,有没有介于现实和虚拟之间的一个交流平台,帮助大家找到更多的好朋友?
每天单调重复的生活,现实世界越来越感觉枯燥,有没有办法 让眼前的现实世界变得更加丰富?
利用AR技术正好可以解决以上各种问题。一个可以改善和满足新需求的技术,难道不将会是下一个热潮?

关于AR的畅想

记得第一次接触到了AR技术时,当时真的是非常激动,因为利用gai可以实现的应用太多了。
大家眼前所看到的不了解的东西,可以即时的了解 到相关的信息,不再需要记住关键信息,拿到电脑前去搜索。
在繁杂的地铁站里,叠加在现实场景之上导向图标,告诉你应该往哪边走,就可以到达目的 地。
在旅游的时候,可以即时的了解观光地的解说,历史,附近的风土人情,旅馆和餐厅等等。
戴上AR的眼镜,人们可以像X战警里面的那样, 即时看到关于眼前的人的相关情报信息。
当然还有很多可以应用场景,留给大家去想像。

AR的历史

人们对AR的研究是从1990年代初期开始,当时需要昂贵的硬件设备。初期主要实现方式采用HMD(Head Mounted Display)。

随着个人电脑的性能不断改进,2007年左右AR的概念开始逐渐的在专门的研究者之外流传开来。
现在小型照相设备的高解析度化,以及各种 Sensor的发展,移动设备上也可以实现AR。HMD设备的进化,越来越接近实用。

AR的适用范围

AR的适用范围比较广,目前在医疗,军事和游戏等领域已经有被使用的实例:
1、互联网: 将现有互联网信息附加显示在现实信息中
2、 游戏娱乐:结合于现实的游戏,让玩家感受更真实
3、医疗:辅助精确的定位手术部位
4、军事:通过方位识别,获取所在地的相关地理信息等
5、 古迹复原:在文化古迹的遗址上进行虚拟原貌恢复
6、工业:工业设备的相关信息显示,比如宽度,属性等
7、电视:在电视画面上显示辅助信息
8、 旅游:对正在观看的风景上显示说明信息
9、建设:将建设规划效果叠加在真实场景,更加直观

AR的实用例 – Future For The Past

荷兰首都的考古学博物馆Allard Pierson Museum,将iMac作为窗口的AR系统MovableScreen,用来展示古罗马的遗迹。

新闻记事: http://www.engadget.com/tag/MovableScreen/
YouTube: http://www.youtube.com/watch?v=0UODkvUTnAU

AR的实用例 – The Eye of Judgment

由索尼公司开发的,结合电视和Trading Card的AR型PS3游戏。将Trading Card作为标志图片,识别后显示相应游戏脚色CG。

游戏网站: http://www.jp.playstation.com/scej/title/eoj/
YouTube: http://www.youtube.com/watch?v=3oGjGlxn0a0

AR的实用例 – 自动跳出的电子书

由日本某印刷公司开发的结合AR电子书,摄像头读取书上的标志图片后,在显示器中显示相应的3D动画。

官方网址: http://www.dnp-digi.com/solution/product/experience/04-3.shtml


AR的实用例 – BMW的MINI汽车

由德国Metaio公司开发的AR型电子杂志,访问BMW的网站后,将杂志 放在摄像头面前,MINI车的CG广告会跳出来。

官方网址: http://www.mini.de/de/de/webcam/index.jsp


AR的实用例 – 古代都市”飞鸟”

东京大学大学院情报学环池内研究所正在开发的项目”虚拟飞鸟京”,是一个采用AR技术的遗址复原项目。

官方网址: http://www.cvl.iis.u-tokyo.ac.jp/research/virtual-asukakyo/


手机上的AR实用例 – Layer

由荷兰的SPRXMobile开发的手机上的AR浏览器Layer。将选择的不动产,Wikipedia和Flickr等Layer,结合GPS信 息等叠加在摄影图像上Android程序。

官方网址: http://layar.com/
YouTube: http://www.youtube.com/watch?v=nkPHDMVxKn0


手机上的AR实用例 – 空间透视手机

空间透视手机根据现在地和手机朝向,采用3D立体的方式显示路标和观光景点等信息,由日本KDDI公司主导开发。

官方网址: http://www.kddilabs.jp/press/detail_102.html


手机上的AR实用例 – Nearest Tube

iPhone 3GS专用的AR地下铁向导程序,App Store上最初的AR程序之一,用来向导伦敦等以复杂出名的地下铁。在摄影图像上显示叠加上前方地下铁车站的信息,如到达车站的距离和路线名等。倾斜之 后可以看到更远的车站的信息。

官方网址: http://www.acrossair.com/apps_nearesttube.htm
YouTube: http://www.youtube.com/watch?v=U2uH-jrsSxs


手机上的AR实用例 – SekaiCamera

由日本Tonchidot(頓智・)公司开发的AR应用。能够在iPhone的摄像头的影像上叠加显示用户提交的评论,图片和动画。2009年9月 份登陆日本的AppStore,仅4天时间下载量达到10万次。

官方网址:http://tonchidot.com/


实现AR的方式之一 – GPS + Sensor

说明: 通过GPS取得纬度、经度和高度,通过地磁Sensor(电子指南针)取得面向的方向,通过加速度Sensor取得倾斜的角度,然后根据这些位置信息获取 相关信息后叠加显示。
必须装置: 摄像头、显示屏、GPS、地磁Sensor(电子指南针)、 加速度Sensor
相关项目: PlaceEngine、SekaiCamera、Layer

实现AR的方式之一 – 原理简介

在手机上实现GPS + Sensor方式的原理图


实现AR的方式之一 – 个人看法

采用这种方式的AR应用,在手机上实现最为合适。因为目前最新的手机比如iPhone 3GS和Anroid上已经具备GPS、地磁Sensor和加速度Sensor这些装置。再加上3G的普及,无线网络的速度提升,基本上不会觉得慢。
目 前存在GPS精度问题,地磁Sensor容易受到周边设备(铁磁器等)的影响。
尽管如此,因为实现简单,目前已经有相关应用逐步登场,采用该方 式的AR将会应该会最先普及。而且准天顶卫星的发射可以将GPS误差可以减小到1cm,以及将来下一代手机通信规格LTE的下行将达到100Mb/S。

实现AR的方式之二 – Marker识别

说明: 将Marker图像的信息事先保存,通过图像识别技术,在当前的图像中查找识别Marker图像,然后在Marker图像上叠加相关信息。
必须装置: 摄像头、显示屏、Marker图像
相关项目: ARToolKit(OpenSource)
ARToolKit网 址: http://www.hitl.washington.edu/artoolkit/

实现AR的方式之二 – 原理简介

采用ARToolKit实现的AR程序截图,黑色的正方形卡片为Marker,显示在Hiro的Marker之上的是虚拟的CG动画。

Marker型AR的程序基本流程是:

1、取得摄像头的图像

2、Marker的识别

3、Marker的位置和方向检测

4、在摄像头的图像上合成CG动画然后显示

实现AR的方式之二 – 个人看法

个人看法:该技术目前相对来说比较成熟,其代表作ARToolKit适用方便简单,而且因为ARToolKit本身开源,容易接触和研究,已经被多 家公司采用。并且同时也被爱好者们在其它平台上实现,比如Flash版、Android版等。
最大缺点在于需要事先定义Marker图像。而且程 序当中无法获取Marker图像以外的信息,功能扩展可能被限制。
适用于游戏,杂志书籍和广告等,在指定的Marker图像上合成CG的场合。

实现AR的技术之三 – 图像分析识别

说明: 对摄影图像进行解析,识别出风景,物体和空间,然后叠加相关信息。
必须装置: 摄像头、显示屏
相关项目: PTAM(OpenSource)、SREngine
PTAM的网址: http://www.robots.ox.ac.uk/~gk/publications.html#2007ISMAR

实现AR的方式之三 – 原理简介

PTAM全名是Parallel Tracking and Mapping,是于ISMAR 2007上,由英国牛津大学的Georg Klein和David Murray发表的一篇论文。下面是其运行时的截图。

其主要原理是: 从摄影图像上捕捉特征点,然后检测出平面,在检测出的平面上建立虚拟的3D坐标,然后合成摄影图像和CG。其中,独特之处在于,立体平面的检测和图像的合 成采用并行处理。

实现AR的方式之三 – 个人看法

不使用特定的信息,通过对图像进行解析,在现实图像上建立虚拟坐标,能够很自然的合成现实图像和CG。可以说是AR研究的一个目标。
目前还 有很多问题有待解决。 比如需要处理数据量大,平面上的障碍物和垂直面的辨析等问题。 技术难度比较大。
离实际使用水平还有一段距离。

AR的市场

AR的市场可以分为特定用途和一般用途。特定用途比如医疗,建设等,面向的是各领域的专家。一般用途比如游戏,向导等,面向的是普通大众。
一 般用途又可以分为面向事物和面向人与人之间。面向事物的AR应用一般是人们有目的的偶尔使用,比如游戏用来娱乐,向导用来帮助自己快速了解一个新的地方。 面向人与人之间的AR应用一般是人们会每天经常适用,比如结合Twitter、Facebook等社交型应用(SNS)的AR。
从一个大的角度来 说,AR应用的市场应该大致可以分为这三类:特定用途型,面向事物的一般用途型和面向人与人之间的一般用途型。这三种类型的AR应用应该会逐步登场,渐渐 融入人们的生活。

AR的创业

我想世界上一定有很优秀的项目,但有的项目需要很长时间的研发,或者说投入很大,有的项目在欧美很合适,但不适合中国的文化背景。有的项目创意很新 颖,但技术方面过于超前,不切合实际等等。所以最好的项目,不一定是最合适的项目。能在合适的时间,根据财力、物力以及人力找到最合适的项目是关键。
AR 技术虽然是一个很有潜力的投资项目,但也不能盲目的去追逐,需要找到一个合适的切入点。目前中国3G牌照的已经发放,中国移动即将要开始出售的 Adnroid系统的OPhone手机,中国联通引入Apple公司的iPhone,实现GPS+Sensor方式的AR应用基本要素已经完全具备。
如 何利用AR技术的优点,结合现有互联网资源,整合或者创造新的应用,发现还未被完全满足或者崭新的需求,是问题的关键。


AR的IDEA之一 – 图片代替文字自动搜索

使用想象图是,用户只需要打开手机的摄像头照一下某物体,程序从摄像头获取的图像进行分析识别之后展开搜索,然后将搜索结果叠加显示在图像上,然后 显示在显示屏上。
利用该程序,用户能够省略打开浏览器,输入关键词,点击搜索按钮的步骤,直接通过摄像头来自动搜索。
该IDEA涉及到技 术最主要难点是图片中物体的识别,一下子实现对任何物体的识别可能有一定难度,我们可以逐步的展开,首先实现对某种类型的物体的识别,比如:书,CD。还 可以和Amazon或者当当网合作等。
OpenCV最近发布了2.0版本,最初由Intel公司开发的一个开源项目,该Library主要用来对 图像的分析识别,性能也不错。
AR的IDEA之二 – 人脸识别记忆

当人的交际网越来越广,朋友越来越多的时候,难免有所忘记。比如当某天偶然遇到以前见过的朋友时, 一下子却想不起名字来,或者根本就不记得,那时可能会比较尴尬,也有可能因此而失去这位朋友。
名片夹里名片太多,可能大部分自己都忘了什么时候收 到的。对方是谁,长什么样子等等,都有可能都忘得一干二净了。
怎么办呢?硬盘上存储的东西,可不会这么容易丢。所以,我们可以做这样一个系统,以 人脸为KEY,建立一个电子名片系统,利用HMD(比如:眼镜)之类的设备来输入人的脸相,再次遇见对方时,其电子名片会自动显示在HMD上。
也 可以将人们在Twitter上的最新发言,显示在人们的头顶上。

AR的IDEA之三 – 虚拟和现实之间的沟通交流平台

在现实生活中,人们随时随地都会有很多说话沟通的欲望。
有的人喜欢说说自己对某事物的看法和意见,并且希望得到他人的认可,或者找到志同道 合的朋友。
有的人喜欢表达自己的意见,比如,在某餐馆吃完饭,觉得这里味道不错,想推荐给其他人。
有的人喜欢写出自己的心情,跟好朋友一 起分享喜怒哀乐。
利用AR可以建立基于手机,GPS,可以在随时随地发表言论,在线的交流,建立朋友关系,自己公开的言论可以显示在真实的摄像中 等。


AR的IDEA之四 – 基于AR的GPS向导

现在的GPS向导器,基本上主要是基于地图来导航。然而对所在地不熟悉的人,有时很难判断眼前所看到的地方,是否和GPS向导器在地图上所显示的地 方相吻合。换句话说也就是不够直观,大家可能都需要想一下,分辨一下东南西北之后才能看明白。
如果能将向导信息,直接叠加显示在人们眼前所看到的 街道上,一定会更加容易理解。GPS通信方式基本不变,改变数据所显示的方式。
现在带有GPS功能的手机越来越多,Android和iPhone 3G S都带有电子指南针和加速度探测器, GPS相关的应用通过手机应该会很容易普及,而不需要另外花钱购买GPS向导器。

最后,文章有点长,非常感谢大家阅读到这里。

备注: 出于学习交流的目的,本文中有部分文字和图片来自互联网,如有侵犯知识产权的地方,请及时与我联系。

[转载]OSGi.NET-based ASP.NET插件平台开发中遇到的一个很有意思的问题

mikel阅读(1300)

[转载]OSGi.NET-based ASP.NET插件平台开发中遇到的一个很有意思的问题 – 道法自然 – 博客园.

到目前为止,我们的OSGi.NET平台已经越来越完善了,因此我开始着手实现基于OSGi.NET的Desktop Plugin Framework和ASP.NET Web Plugin Framework。Desktop Plugin Framewok比较简单,可以直接将OSGi集成到启动类里面,不过在ASP.NET遇到了一个很有意思的问题。
以 下是Web Plugin Framework的结构,它只是暴露出一个Default.aspx页面,这是一个完全空白的页面,根据具体应用由Plugin来填充内容。 Global.asax用于启动和停止OSGi框架。bin目录包含了对OSGi.NET类库的引用。
WebPluginFramework
–Default.aspx
–Global.asax
–bin
——–WebPluginFramework.dll
——–UIShell.OSGi.dll
——–UIShell.OSGi.Web.dll
–Plugins
——–TestPlugin(使 用VS创建的ASP.NET网站项目)
—————–bin/lib/share…*.dll
—————–Default.aspx
—————–Manifest.xml
——–TestPlugin2(使 用VS创建的ASP.NET网站项目)
—————–bin/lib/share…*.dll
—————–Default.aspx
—————–Manifest.xml
我 们在设计用户开发和调试插件的场景如下:(1)用户在Plugins目录下创建一个ASP.NET网站工程项目,比如TestPlugin项目;(2)添 加Manifest.xml文件,设置插件的名称、插件运行时类库、插件激活器、插件注册的服务;(3)用户可以通过2种模式调试插件:A)将 WebPluginFramework部署到IIS,利用附加进程到w3wp.exe进行调试;B)用户打开WebPluginFramework项目, 利用VS直接进行调试。
这种模式听起来我觉得还不错,但是当我运行Web应用时,一旦加载 TestPlugin/Default.aspx时,我便遇到一个Parser Error错误。这个错误指示无法加载TestPlugin/Default.aspx绑定的类TestPlugin._Default。

起初我以为是类加载机 制出了问题,因为OSGi.NET实现了一套插件类加载机制,确保能够从插件的私有程序集中加载到相应的类,即OSGi.NET能够从 TestPlugin/bin等目录下加载到所需的类。然后经过初步调试和判断,发现问题出在ASP.NET的Compilation。ASPX页面是在 请求时动态编译成相应的dll,这个动态编译是由BuildManager来实现,它在编译时无法找到TestPlugin/bin /TestPlugin.dll,也就无法找到TestPlugin._Default这个类了。我搜索了一下相关解决方案,发现搞插件平台的国内外同行 也遇到过类似问题,但是都没有给出一个好的方案。我还查看了Kooboo的插件方案,看能否借鉴点思路。Kooboo CMS开源系统在解决这个问题的时候,采用了很简单的方式,即将TestPlugin.dll直接拷贝到WebPluinFramework/bin目录 底下。然而,我不能这样做,因为这种做法破坏了OSGi.NET优雅的类加载机制,我希望能够在保持这种优雅类加载前提下,实现ASP.NET插件框架。
于 是我下载了.NET框架的PDB,直接Debug进去到BuildManager,发现BuildManager编译ASPX页面的原理,该类在 BuildManager.CompileWebFile实现ASPX页面动态编译,看完源码后,我想到一种简单的解决方法,就是在编译这个页面之前利用 反射将TestPlugin.dll添加到BuildManager.TopLevelReferencedAssemblies私有属性。这种方法最终 解决了ASP.NET插件的实现问题。
1 var assemblyFile = context.Request.PhysicalApplicationPath +
2 @”\\plugins\\TestPlugin\\bin\\TestPlugin.dll;
3 Assembly assembly = Assembly.LoadFile(assemblyFile);
4 IList<Assembly> assemblies = null;
5 PropertyInfo buildManagerProp = typeof(BuildManager).GetProperty(TheBuildManager,
6 BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.GetProperty);
7 if (buildManagerProp != null)
8 {
9 BuildManager buildManager = buildManagerProp.GetValue(null, null) as BuildManager;
10 if (buildManager != null)
11 {
12 PropertyInfo toplevelAssembliesProp = typeof(BuildManager).GetProperty(
13 TopLevelReferencedAssemblies, BindingFlags.NonPublic |
14 BindingFlags.Instance | BindingFlags.GetProperty);
15 if (toplevelAssembliesProp != null)
16 {
17 assemblies = toplevelAssembliesProp.GetValue(
18 buildManager, null) as IList<Assembly>;
19 }
20 }
21 }
22 if (assemblies != null && assembly != null)
23 {
24 assemblies.Add(assembly);
25 }

Creative  Commons License

本文基于Creative Commons Attribution 2.5 China Mainland License发布,欢迎转载,演绎或用于商业目的,但是必 须保留本文的署名道法自然(包含链接)。如您有任何疑问或者授权方面的协商,请给我留言。

[转载]一步步打造基于ASP.NET的CMS内容管理系统--Step4 权限设定(补充)

mikel阅读(952)

[转载]一步步打造基于ASP.NET的CMS内容管理系统–Step4 权限设定(补充) – 会议室预订系统 企业内部网站集ASP.NET CMS与ASP.NET OA一体的通用管理系统 – 博客园.

Dotnetcms Portal基本上是我在cnblogs文章的一个整合,DEMO演示地址

Demo: http://portal.dotnetcms.org

本文将介绍如何使用权限。

关于权限,网上有很多了,不过,Dotnetcms的权限控制方式主要采用配置方式,也就是精确到页面,在

web.config里,可以定义

<authorization>
<allow roles=”administrators;users” />
<deny users=”*”></deny>
</authorization>

类似这样的访问方式,那么是怎么实现的呢?.NET本身是不支持allow这样角色的,为此我们需要实现IPrincipal接口(好像MSDN上 有文章介绍)

public class CustomPrincipal : IPrincipal { private IIdentity _identity; private string[] _roles; public CustomPrincipal(IIdentity identity, string[] roles) { _identity = identity; _roles = new string[roles.Length]; roles.CopyTo(_roles, 0); Array.Sort(_roles); } // IPrincipal Implementation public bool IsInRole(string role) { return Array.BinarySearch(_roles, role.ToLower()) >= 0 ? true : false; } public IIdentity Identity { get { return _identity; } } public bool IsInAllRoles( params string [] roles ) { foreach (string searchrole in roles ) { if (Array.BinarySearch(_roles, searchrole.ToLower()) < 0 ) return false; } return true; } public bool IsInAnyRoles( params string [] roles ) { foreach (string searchrole in roles ) { if (Array.BinarySearch(_roles, searchrole.ToLower()) >= 0) return true; } return false; } /// <summary> /// 角色列表,多个角色以分号分隔 /// </summary> /// <param name="roles"></param> /// <returns></returns> public bool IsInAnyRoles(string roles) { return IsInAnyRoles(roles.Split(new char[] { ';' })); } }

然后在登录页面里,实现身份验证票,也就是把roles存到cookie里,这样就可以了

string Roles = "roles"; FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,uid.ToString(),DateTime.Now,DateTime.Now.AddHours(2), true,Roles); string encryptedTicket = FormsAuthentication.Encrypt(ticket); HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName,encryptedTicket); authCookie.Path = FormsAuthentication.FormsCookiePath; authCookie.Expires = DateTime.Now.AddHours(2); Response.Cookies.Add(authCookie);

这样还可以利用 Users.IsUserInRoles(“adminstrator”);判断用户是否在角色里

注意:这里仅是权限管理的一种方式,事实上,完全可以不适合任何角色,直接在Page_Load里检测用户的权限例如

SQL=”select * from roles where username=’xx'”

直接读取角色,然后对roles进行处理,他的好处简单,但是缺点:

1)无法充分利用.NET提供的便利功能
2)每次判断都需要到数据库里读roles,显然没有.NET提供的读取coookie快

当然,我们也可以自定义吧roles存到cookie,但是还需要对cookie进行一些处理也是比较麻烦

—————————————-

以下是广告时间:

经过两周的努力,Dotnetcms Portal系统终于上线了,
下载地址为:
http://www.dotnetcms.org/bbs/showforum-2.aspx

使用指南:新手必看,否则不知道如何使用本系统 http://www.dotnetcms.org/userguid/use1.html

Demo: http://portal.dotnetcms.org

[转载]Nullable 类型的转换

mikel阅读(991)

[转载]Nullable 类型的转换 – KKcat ^_^ – 博客园.

一直都是个头疼的问题,感谢maplye

1.The PumaCode.org Blog

public object ChangeType(object value, Type conversionType)
{
    if ( conversionType.IsGenericType &&
        conversionType.GetGenericTypeDefinition( ).Equals( typeof( Nullable<> ) ) ) {
 
        if(value == null)
            return null;
 
        System.ComponentModel.NullableConverter nullableConverter
            = new System.ComponentModel.NullableConverter(conversionType);
 
        conversionType = nullableConverter.UnderlyingType;
    }
 
    return Convert.ChangeType(value, conversionType);
}

引用:http://blog.pumacode.org/2006/05/18/using-convert-changetype-on-nullable-types/
2.Paul Wilson’s .NET Blog

public class DataTypeConverter
    {
        public static object ChangeType(Type type,object value)
        {
            if ((value == null) && type.IsGenericType)
            {
                return Activator.CreateInstance(type);
            }
            if (value == null)
            {
                return null;
            }
            if (type == value.GetType())
            {
                return value;
            }
            if (type.IsEnum)
            {
                if (value is string)
                {
                    return Enum.Parse(type, value as string);
                }
                return Enum.ToObject(type, value);
            }
            if (!type.IsInterface && type.IsGenericType)
            {
                Type type1 = type.GetGenericArguments()[0];
                object obj1 = DataTypeConverter.ChangeType(type1,value);
                return Activator.CreateInstance(type, new object[] { obj1 });
            }
            if ((value is string) && (type == typeof(Guid)))
            {
                return new Guid(value as string);
            }
            if ((value is string) && (type == typeof(Version)))
            {
                return new Version(value as string);
            }
            if (!(value is IConvertible))
            {
                return value;
            }
            return Convert.ChangeType(value, type);
        }
    }
引用:http://weblogs.asp.net/pjohnson/archive/2006/02/07/437631.aspx

[转载]ASP.NET MVC:AutoController--通用自动增删查改

mikel阅读(1070)

[转载]AutoController–通用自动增删查改 – 撞破南墙 – 博客园.

我的春秋痴梦第三步:

让所有的增删查改自动化,

不用每次都实例化一个controller然后郁闷的写增删查改的方法。

效果:

根据url 得到要操作的对象,自动对model属性过滤换转,完成CURD生成对应的回传给view。

我的思路:

@1 根据 提交的 url    /admin/article/create
@2 实例化 article 对象
@3 根据 admin 和 用户找到  article 的限制
@4 根据限制 过滤article
@5 再交给autoaction 即 直接 增改
@6 保存和上传(图片 文章 的话)
@7 根据url对象返回页面

@1 分析提交的 url    /admin/article/create   得到对象

@2 实例化 (article) 对象

@3 根据 (article)对象 权限 找到  (article) 的限制(使用xml)

@4 根据限制 过滤(article)对象

@5 转换和上传(有图片之类的话)

@6 再交给auto controller即直接 增改

@7 根据url对象返回页面

整个想法比较简单。和之前那篇一样让程序更通用更自动化。

其实一开始接触MVC的时候就又这个想法了,当时没想好该怎么做增改这两 块。

前段时间看了老D分析Suteki.Shop 介 绍了 里面一个自动脚手架

两个周前我把他定下计划之中 I will make it!

废话不多说。

@1 可以想我一样自己分析url  也可以取得 route 里面的 controller的值

按指定 格式 切割 字符串

当然 在这里 我并不推荐这样.直接取值更方便。

@2 实例化(article) 对象并装配进去表单的值

代码

在上一篇中我提到了不知道如何转换可空类型后来发现了这 篇文章 感谢乐于分享的牛人们。(感谢cnblogs)

其中一个自己写的Init的扩展类似前一篇,就不瞎贴代码了。获得一个对象类似前 一篇的根据string类型,

得到类型都是静态工厂直接new一个出来。

@3 根据(article)对象用户的权限 找到  (article) 的限制和转换(使用xml)

@3.1 我这里选择了xml没有使用加attribute的方式,为的是更改后不用重新编译。

modelfilter

这里我的想法比较多,但是很多都还没有实现。

比如根据url生成验证的id版本,根据权限选择不同的url,类似html的css 可以自定义 验证和转换。

@3.2 再来看看 Linq 2 XML 进行读取和解析 xml

Linq2XML

我的xmlHelper

XMLHelper

比较简陋 。。我是实用主义。

这样我们就可以在

foreach (var element in fileups){}// <DownUrl fileup="/UpFile/,*,上传失败" />  

中间专心写自己的处理了。

@3.3延迟执行

在这里的时候我,我突然发现如果在这里以检查上传没问题就即时上传的话,可能他并没有通过后面的检查

而导致白白的消耗性能和硬盘。这里我先把要执行的方法先保存起来。(感谢重典兄的指点)

private List<Func<bool>> functions = new List<Func<bool>>();

@4 根据限制 过滤(article)对象

使用非常简单 依次添加,遍历执行即可。整个东西有3个需要做的,

第一个是文件上传

第二个指定转换

第三个验证,验证你也可以用其他的,比如分离自带的那个验证为自己用。

文件上传

指定方法

验证我这里就没有贴了比上面两者都简单 。

指定方法的执行我采用的是调用我指定的一个对象下面的方法,本来 是想调用任意的,在xml那里写全对象的

namespace那些就可以调用,后来还是放弃了,感觉不实用。方法也只是一个单参数的方法。比如

md5加密和指定转换 普通用户发的文章为 未审核等。

@5 保存和上传(有图片之类的话)

var result = functions.All(a => a.Invoke());//都满足 返回 true

@6 再交给auto controller即直接 增改

var ir = IRR.GetRepository(urlHelper.modelName.str2type()); ir.InsertOnSubmit(model); ir.SubmitChanges();

@7 根据url对象返回页面。这里给出我在controller中的实现:

代码

提醒一下

1暂时那个    ModelState.IsValid 验证没有任何意义,因为传进来的model是object。

2如果不知道我那个 public IRepository Ir;怎么出来的可以参见我的上一篇。

在这里可以写成

public class AutoController<T> : Controller where T : class, new()

就可以使用验证了。我不这样做是因为 我不想每次都要实例化一个。来模板继承。

但是目前我采用的做法仍然是 依靠一个 具体类 继承 我那个自动类。然后让原本访问具体类的增删查改方法的

去访问自动类了。而这时我们只需要在xml里面写明规则那么一切OK。

至此 问题依然 没有解决 那就是 由于 依托于具体类继承自动类。每次还是得实例化类。

(我是后台:可不可以不要那么多XXXcontroller文件啊!)

我们其实可以在 Global.asax 那里 拦截请求。

不给controller执行 而是 构造自己的 RouteHandler 解析路由 和HttpHandler 转到view

此处参考重典兄的 route

实现的思路

1在 global 处 截断 指定格式的请求

2构造自己的RouteHandler 进行解析

3 分发给对应的 httphandler

public static void RegisterRoutes(RouteCollection routes) { routes.Add(new Route("{page}.np", new MyRouteHandler()));//解析.aspx }

我指定以.np后缀访问的都是我自动类处理的天下。

我的想法是这样  /area(Default)/model/action/id.np

同时对应view页面的位置统一。其实现非常简单。参看重典兄的就可以做出来。

这时我们会遇到几个问题

1如何传值到页面 viewdata 肯定不行了,因为controller与我们渐行渐远。

2此时view上的XXhelper ,HTML 的方法都没了,以前构造html的方法都没了。

对于第一点 我们可以使用session 只需要加上IRequiresSessionState

public class MyPage : IHttpHandler, IRequiresSessionState

这里有个有趣的问题,但是我又不知道怎么回事。在构造函数执行的时候,session null不可用。

而构造完后才开始有session。这是怎么回事呢。

对于第二点我就有点无奈了。我参考源码,暂时还没把他整个方法移植过来。郁闷的是,由于那个是模板的,而我们

已经没有了页面MODEL了。所以比较麻烦。很多方法里也内嵌了MODEL。即时勉强移植过来还是会存在一些不能用的

语法但是又存在。感觉不爽。

走到这里的时候我感觉到很吃力。 丢掉了controller之后,我还有一 大堆view丢不掉,还有怎么和以前的路由兼容?

不然的话整个view上路由的重写也有点小麻烦。

感觉使用框架的便利,自己也被绑架到框架上了。自己的代码原始积累应该是与框架无关的啊!这样才能形成自己的

代码库。

最后由于无法丢弃以前的view暂时使用前一种方法,来优化自己的 CMS。走到第三步。这次的感觉有点沉重。

越发觉得自己的渺小和对知识认识的浅薄,当时以为委托 很简单,自己遇到了问题需要解决才发现,好多其实都

没有理解好。另一点沉重也许是对未来的一种迷茫吧。基于框架的代码还是我们的代码吗?昨天有个牛人说他为了

不让自己的代码有隐患,尽量不用第三方的框架SSH那些都不用。而是做自 己的基于基础类库的原始积累。

也许JAVA和.NET不一样吧。我始终坚持,一步一步优化,一步一步积 累。把时间精力花在最需要解决问题的地方,

慢慢的用自己的代码替换别人的框架,像我这次这样替换掉 controller,也许有一天就用自己的东西替换了

mvc了呢。虽然没有mvc的通用,但最合适自己。不是嘛?



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

[转载]ASP.NET MVC:局部化页面和效率CMS实践系列总结

mikel阅读(867)

[转载]局部化页面和效率CMS实践系列总结 – 撞破南墙 – 博客园.

对于重复开发,我们懒人最是厌恶了。特别是在硬件摩尔的时代。

真的是很讨厌写很多重复函数的东西比如:

public class ArticleController : Controller { //TO DO //C U R D 接下来还有 Channel Comment User .... }

接下来还有 Channel Comment User ..

写到你手抽筋。。。。也许你会说把repeat丢给自动生成工具。不仅有些是不必要的代码,而且还不符合DRY原则。

但是这里我要表达的不是 Controller而是存在与 view 上的一些重复的开发。

有时候我们会遇到这样的场景:

添加一个文章但是需要选定他所属的栏目(外键关系)

此时如果用我前两篇的做法就会不符合需求。

方法一:覆盖 重写

父类中

[HttpGet] public virtual ActionResult Create() { return View(); }

ArticleController中

public class ArticleController : AbatractAutoController { public ActionResult create() { var channelManager = IRR.GetRepository(typeof(Model.KF_Channel)); ViewData["ChannelList"] = channelManager.GetAll(); return View(); } }

这样做是很简单也很没有效率的事情接下来你会面临

需要添加一个 文章的审核者(假设又一个外键关系),与此相关联的文 章(需要从数据库读出一些文章以供选择)等等。

当然你可以打起精神来。。继续在ArticleController添加.

此时新的需求来了:

在一个另外 创建栏目的页面里需要重复用到 栏目列表。

此时 我们可以 选择

1 copy 把得到栏目列表的 方法 copy到 channelController 里面。

2 (更好的)把得到栏目列表的方法写出函数 以供 调用。

即使是方法2 任然 破坏了我们当初的设想 autoController 自动增删查改。

此时我们可以用

方法二:局部页面的方法

在不破坏原有 controller 上,在页面采用 Html.RenderAction 方法。

他会以请求url的方式得到一个页面并加载到当前位置。

这样的话我们就可以把粒度细化到一个html控件上。

<select name="需要绑定的name"> <% PagerHelper.GetSelect(this, "Channel", "Type1"); %> </select>
//====== 面向html 控件编程 public static void GetSelect(ViewPage viewPage, string ModelName, string KJName) { RouteValueDictionary routeValueDictionary = new RouteValueDictionary(); routeValueDictionary.Add("ModelName", ModelName); routeValueDictionary.Add("KJName", KJName); viewPage.Html.RenderAction("GetSelect", "UserKJ", routeValueDictionary); }

由于历史遗留问题(刚开始做项目的时候使用了辅助类)我这里使用的是辅助类,你也可以用自定义扩展,那么在view

上就可以少写一个 this。各有好处吧。

然后去后台写一个操作。

代码

public ActionResult GetSelect(string ModelName, string KJName) { SearchPage searchPage = new SearchPage(); searchPage.ModelName = ModelName; IRepository ir = IRR.GetRepository( searchPage.ModelName.str2type()); //根据类型得到要操作的类 searchPage.model = searchPage.ModelName.str2type(); var iq = ir.GetAll();// 得到所有 return View("Select" + KJName, iq); }

再去到返回的view上写具体的显示

<%var Mod = Model as IQueryable<KF_Channel>;%> <%foreach (var kfChannel in Mod) {%> <option value="<%=kfChannel.ChannelID %>"> <%=kfChannel.ForderName %></option> <% }%>

整个方法比较笨拙,

当需求重复的时候

方法一:破坏autocontroller 调用一个方法。在页面上添加一个 viewdata[“channellist”]

方法二:我们需要在页面上加一个

<% PagerHelper.GetSelect(this, Channel, Type1);%> 必要的开销

显示重复的时候还是可以省点力的。

从controller得到的数据的处理

方法一 需要在辅助方法里面 StringBuilder 然后把html写死。类似这种

代码

public static string DDListByVD(List<SelectListItem> vdd, int FatherChannelID) { StringBuilder sb = new StringBuilder(); foreach (SelectListItem listItem in vdd) { if (listItem.Value == FatherChannelID.ToString()) sb.Append("<option selected=\"true\" value=" + listItem.Value + " >" + listItem.Text + "</option>"); else sb.Append("<option value=" + listItem.Value + " >" + listItem.Text + "</option>"); } return sb.ToString(); }

方法二 因为写在一个view里面跟方便前后台的分离,交给美工给大的权利(我是前台:不用每次都找后台改了。。)

更主要的是需求更改之后 比如说 需要排序 ,字符串处理   ,甚至显示方式的改变(从下拉到 列表框)

起码可以临时解决在页面上的尖括号中进行处理。虽然这样不符合 轻快的view的原则 , 也可以在写在辅助方法里面。

其实对于这个我最想要的效果是这样。

前台对于简单的下拉框要求只需要传入参数,1实体名 (Articles),2过滤的条件和排序,3返回的格式和属性 key-value

由于我使用的是Linq 2 SQL得到的只是iquery 对象,查了csdn发现不能即时查询出结果,所以无法得到指定的一个字段,

其实按道理是可以的。但是由于我们操作的是一个父类,子类的东西用不了。遗憾啊。

我的MVC2.0+Linq2SQL 的CMS提升开发效率的几个想法和实现的到这里算告一段落了。

其中第一步没有写出来其实就在第一篇里面已经提到的那个对Linq封装CURD那一层的那个优化,由于都是参考

Suteki.Shop的没有多少自己的想法,也就不写了。

现在来梳理一下我整个的春秋痴梦:(mvc+linq2SQL)CMS提升开发效率的几个想法和实践。

整个的目的是:提高开发效率,主要是后台的开发同类 新需求的效率。

实现的办法是:做一个通用的增删查改列的前台使用入 口。让实现新业务需求的代码量降到我能达到的最低。

几乎把重复的能封装重用的都省了。所有的要写的都是必须得写的,我叫做必 要开销。

真的是一次编写,所有的实体类通用。

当然整个的功能不是很强,也没有考虑很多性能,安全。但是实现自己的想法也确实提高了效率。

觉得真的很爽。一个月前规划了整个的优化的计划。一个星期实现一个基本达到了。到现在也没有什么新的想法。

下一步准备学习Silverlight。环境也装好了。教程也下载好了。项目也找好了。:)  。简单的来说就是把CMS移植

到SL上去。

以后我一周还是至少会发一篇随笔。坚持写下去。向老赵大哥,未鹏大哥学习,坚持原创,坚持思考。

我的前两篇 春秋痴梦。

第一步:接口+泛型强大的Repository

第二步:泛型通用动态查询(LinQ+Ajax)

第三步:AutoController–通用自动增删查改

第四步:局 部化页面满足特殊要求

[转载]SQL开发好助手—SQL Assistant 5

mikel阅读(1034)

[转载]SQL开发好助手—SQL Assistant 5 – Kevin – 博客园.

支持多种数据库:MS SQL、MySQL、Oracle、DB2;

下址地址:http://files.cnblogs.com/kevin-wang/SQLAssistant5.0.97.rar

1.提供友好的语法格式化、智能提示;(这也是最让开发者喜欢的功能)

image

image

2.代码格式化:

image

3.代码鸟瞰图

image

4.自动生成编程语言的语法格式

image

5.生成测试数据,保存成脚本文件或直接保存到数据库中

image

6.将数据导出为SQL脚本;

image

[转载]ASP.NET MVC 多套皮肤解决方案

mikel阅读(927)

[转载]ASP.NET MVC 多套皮肤解决方案 – Daniel Chow’s Blog – 博客园.

原理自己写了一个ThemeController继承于Controller,先看我的文件结构:

ThemeController的具体实现:

public class ThemeController : Controller
{
private static string[] sPathTemplate = new string[]{
/Themes/{0}/{1}/{2}.aspx,
/Themes/{0}/Shared/{1}.Master};

private static ThemeController m_ThemeController = new ThemeController();

public static ViewResult View(string sTheme, string sController, string sView, string sMaster, object oModel)
{
string sViewPath = string.Format(sPathTemplate[0], sTheme, sController, sView);
string sMasterPath = string.Format(sPathTemplate[1], sTheme, sMaster);

return m_ThemeController.View(sViewPath, sMasterPath, oModel);
}

public static ViewResult View(string sTheme, string sController, string sView, object oModel)
{
return View(sTheme, sController, sView,Site , oModel);
}

public static ViewResult View(string sTheme, string sController, string sView)
{
string sViewPath = string.Format(sPathTemplate[0], sTheme, sController, sView);
string sMasterPath = string.Format(sPathTemplate[1], sTheme, Site);

return m_ThemeController.View(sViewPath, sMasterPath);
}

//protected override ViewResult View(string viewName, string masterName, object model)
//{
// if (viewName == null && model != null)
// viewName = model.GetType().Name.ToLower().Replace(“model”, “view”);

// return base.View(viewName, masterName, model);
//}
}

调用方法:

[HandleError]
public class HomeController : ThemeController
{
public ActionResult Index()
{
string m_sTheme = default; //默认的皮肤

if (Request.QueryString[Theme] != null)
{
m_sTheme
= Request.QueryString[Theme];
}

ViewData[Message] = Welcome to ASP.NET MVC!;

return View(m_sTheme, Home, Index);
}

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

显示效果:

自己觉得这是一个比较粗浅的解决方案,算是抛砖引玉吧,敬请多多拍砖!