欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

详解ASP.NET MVC 解析模板生成静态页(RazorEngine)

程序员文章站 2023-11-30 09:12:04
简述 razor是asp.net mvc 3中新加入的技术,以作为aspx引擎的一个新的替代项。在早期的mvc版本中默认使用的是aspx模板引擎,razor在语法上的确不...

简述

razor是asp.net mvc 3中新加入的技术,以作为aspx引擎的一个新的替代项。在早期的mvc版本中默认使用的是aspx模板引擎,razor在语法上的确不错,用起来非常方便,简洁的语法与.net framework 结合,广泛应用于asp.net mvc 项目。

我们在很多项目开发中会常常用到页面静态化,页面静态化有许多方式,最常见的就是类似很多php cms种使用的 标签替换的方式(如:帝国cms、ecshop等),还有很多都是伪静态,伪静态我们就不做过多解释,通过路由或url重写来实现就可以了。razor为我们提供了更加方便的模板解析方式,任何东西都是两方面的,技术也是如此,razor解析模板虽然更加方便、简洁,但是对于模板制作人员来说也是有一定的技术要求,或者对于开发一套模板制作功能来说,考虑的要更多一些。我们不再去探究这些问题,我们更注重哪种技术更容易、更方便、更好的满足我们项目的需求。

如何使用razorengine

今天来简单介绍一下如何使用razorengine解析模板生成静态页面,razorengine它是基于微软的razor之上,包装而成的一个可以独立使用的模板引擎。也就是说,保留了razor的模板功能,但是使得razor脱离于asp.net mvc,能够在其它应用环境下使用,项目地址:razorengine_jb51.rar

首先我们去codeplex上下两个需要的dll

详解ASP.NET MVC 解析模板生成静态页(RazorEngine)

看到网上很多介绍razorengine的基础用法的,讲解的都比较详细,对于razorengine运行原理很清晰,我们在这里就不重复介绍了。写这篇文章是对于很多新手同学来说比较喜欢“拿来主义”,基本的用法原理都能看懂,但是如何应用到项目中还是有些不是很清晰,我们只讲讲如何在项目中运用。

本文分为两部分:第一个部分,基本的单数据模型模板解析;第二部分,面向接口的多数据模型模板解析

第一个部分 基本的单数据模型模板解析

一、我们创建一个mvc项目,并且添加上面的两个dll引用,然后我们新建一个简单的文章类

public class articles
  {
    /// <summary>
    /// 文章id
    /// </summary>
    public int id { get; set; }
    /// <summary>
    /// 文章标题
    /// </summary>
    public string title { get; set; }
    /// <summary>
    /// 文章内容
    /// </summary>
    public string content { get; set; }
    /// <summary>
    /// 作者
    /// </summary>
    public string author { get; set; }
    /// <summary>
    /// 发布时间
    /// </summary>
    public datetime createdate { get; set; }
  }

二、我们新建一个razor的html模板

<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
  <title>@model.title</title>
</head>
<body>
  <h1>@model.title</h1>
  <p>作者:@model.author - 发布时间:@model.createdate</p>
  <p>@raw(model.content)</p>
</body>
</html>

说明:model就是我们的文章实体类  在mvc的试图页cshtml中 我们一般都是在控制器里传递这个实体类 然后在视图页中 @model models.articles 来接收这个实体类 然后通过“@model.”来输出内容,在razor模板中是一样的,只是不用@model models.articles 来接收了,其它的语法跟在.cshtml试图页中是一样的,这么说多余了,因为写法不一样他就不是razor了

三、我们写一个方法来获取模板页的html代码

 /// <summary>
    /// 获取页面的html代码
    /// </summary>
    /// <param name="url">模板页面路径</param>
    /// <param name="encoding">页面编码</param>
    /// <returns></returns>
    public string gethtml(string url, system.text.encoding encoding)
    {
      byte[] buf = new webclient().downloaddata(url);
      if (encoding != null) return encoding.getstring(buf);
      string html = system.text.encoding.utf8.getstring(buf);
      encoding = getencoding(html);
      if (encoding == null || encoding == system.text.encoding.utf8) return html;
      return encoding.getstring(buf);
    }

    /// <summary>
    /// 获取页面的编码
    /// </summary>
    /// <param name="html">html源码</param>
    /// <returns></returns>
    public system.text.encoding getencoding(string html)
    {
      string pattern = @"(?i)\bcharset=(?<charset>[-a-za-z_0-9]+)";
      string charset = regex.match(html, pattern).groups["charset"].value;
      try { return system.text.encoding.getencoding(charset); }
      catch (argumentexception) { return null; }
    }

四、我们写一个方法 用于生成html静态页

 /// <summary>
    /// 创建静态文件
    /// </summary>
    /// <param name="result">html代码</param>
    /// <param name="createpath">生成路径</param>
    /// <returns></returns>
    public bool createfilehtmlbytemp(string result, string createpath)
    {
      if (!string.isnullorempty(result))
      {
        if (string.isnullorempty(createpath))
        {
          createpath = "/default.html";
        }
        string filepath = createpath.substring(createpath.lastindexof(@"\"));
        createpath = createpath.substring(0, createpath.lastindexof(@"\"));
        if (!directory.exists(createpath))
        {
          directory.createdirectory(createpath);
        }
        createpath = createpath + filepath;
        try
        {
          filestream fs2 = new filestream(createpath, filemode.create);
          streamwriter sw = new streamwriter(fs2, new system.text.utf8encoding(false));//去除utf-8 bom
          sw.write(result);
          sw.close();
          fs2.close();
          fs2.dispose();
          return true;
        }
        catch { return false; }
      }
      return false;
    }

五、我们来写个方法调用静态模板,并且传递数据模型实体类 创建html静态页

/// <summary>
    /// 解析模板生成静态页
    /// </summary>
    /// <param name="temppath">模板地址</param>
    /// <param name="path">静态页地址</param>
    /// <param name="t">数据模型</param>
    /// <returns></returns>
    public bool createstaticpage(string temppath, string path, razorenginetemplates.models.articles t)
    {
      try
      {
        //获取模板html
        string templatecontent = gethtml(temppath, system.text.encoding.utf8);

        //初始化结果
        string result = string.empty;

        //解析模板生成静态页html代码
        result = razor.parse(templatecontent, t);

        //创建静态文件
        return createfilehtmlbytemp(result, path);
      }
      catch (exception e)
      {
        throw e;
      }
    }

好了,大功告成,是不是很简单。

详解ASP.NET MVC 解析模板生成静态页(RazorEngine)

这里只是一个很简单的应用,没有读取数据,也没有列表,只有一个文章数据模型,下一部分我们将介绍 多模型模板解析,因为是多模型 所以 生成静态页面的时候 就不是传递一个具体模型实体类 我们会用到 反射,通过反射模型属性 获取数据,有不熟悉反射的可以提前研究一下,也可以直接看下一部分的反射代码也很简单的。

第二部分 面向接口的多数据模型模板解析

这一部分,我们介绍使用接口来解析模板,包括列表等多种模型解析,用到了spring注入和反射还有接口等,有不熟悉的可以百度搜一下或者评论留言。

我们接着上面的示例,我们新建两个类库 一个是存放数据模型的 我们叫domain;另外一个是接口和实现类的 我们叫service,然后我们添加他们之间的引用

详解ASP.NET MVC 解析模板生成静态页(RazorEngine)

一、我们在domain下创建几个测试类

articles - 文章测试类

company - 公司测试类

column - 栏目测试类

templateview - 模型解析类(这个是不是比较弱智?我也没深入研究多个模型怎么反射出来 所以 我加了这么个算是公用的类 没有对应的数据表 只是解析模板的时候 作为中间件用用)

   public class articles
  {
    /// <summary>
    /// 文章id
    /// </summary>
    public int id { get; set; }
    /// <summary>
    /// 文章标题
    /// </summary>
    public string title { get; set; }
    /// <summary>
    /// 文章内容
    /// </summary>
    public string content { get; set; }
    /// <summary>
    /// 作者
    /// </summary>
    public string author { get; set; }
    /// <summary>
    /// 发布时间
    /// </summary>
    public datetime createdate { get; set; }
  }
  public class company
  {
    /// <summary>
    /// 公司id
    /// </summary>
    public int id { get; set; }
    /// <summary>
    /// 公司名称
    /// </summary>
    public string companyname { get; set; }
    /// <summary>
    /// 公司电话
    /// </summary>
    public string companytel { get; set; }
    /// <summary>
    /// 联系人
    /// </summary>
    public string contectuser { get; set; }
    /// <summary>
    /// 创建时间
    /// </summary>
    public datetime createdate { get; set; }
  }
   public class column
  {
    /// <summary>
    /// 栏目id
    /// </summary>
    public int id { get; set; }
    /// <summary>
    /// 栏目名称
    /// </summary>
    public string title { get; set; }
    /// <summary>
    /// 文章列表
    /// </summary>

    public virtual icollection<articles> articles { get; set; }
  }
   public class templateview
  {
    /// <summary>
    /// id
    /// </summary>
    public int id { get; set; }
    /// <summary>
    /// 标题
    /// </summary>
    public string title { get; set; }
    /// <summary>
    /// 内容
    /// </summary>
    public string content { get; set; }
    /// <summary>
    /// 作者
    /// </summary>
    public string author { get; set; }
    /// <summary>
    /// 时间
    /// </summary>
    public datetime createdate { get; set; }    
    /// <summary>
    /// 公司名称
    /// </summary>
    public string companyname { get; set; }
    /// <summary>
    /// 公司电话
    /// </summary>
    public string companytel { get; set; }
    /// <summary>
    /// 联系人
    /// </summary>
    public string contectuser { get; set; }
    /// <summary>
    /// 文章列表
    /// </summary>
    public virtual icollection<articles> articles { get; set; }
  }

二、我们在service下创建一个基础操作接口以及其实现类(里面的很多方法 比如:获取页面的html代码、获取页面的编码以及创建静态文件等 是没有必要写在接口的 这个可以写到公用的类库里,因为这里就用到这么几个方法 所以我没有加公用类库 就直接写在这里面了)

/// <summary>
  /// 基础操作接口
  /// </summary>
  /// <typeparam name="t"></typeparam>
  public interface irepository<t> where t : class
  {
    /// <summary>
    /// 解析模板生成静态页
    /// </summary>
    /// <param name="temppath">模板地址</param>
    /// <param name="path">静态页地址</param>
    /// <param name="t">数据模型</param>
    /// <returns></returns>
    bool createstaticpage(string temppath, string path, t t);    

    /// <summary>
    /// 获取页面的html代码
    /// </summary>
    /// <param name="url">模板页面路径</param>
    /// <param name="encoding">页面编码</param>
    /// <returns></returns>
    string gethtml(string url, system.text.encoding encoding);

    /// <summary>
    /// 获取页面的编码
    /// </summary>
    /// <param name="html">html源码</param>
    /// <returns></returns>
    system.text.encoding getencoding(string html);

    /// <summary>
    /// 创建静态文件
    /// </summary>
    /// <param name="result">html代码</param>
    /// <param name="createpath">生成路径</param>
    /// <returns></returns>
    bool createfilehtmlbytemp(string result, string createpath);
  }
/// <summary>
  /// 基础接口实现类
  /// </summary>
  /// <typeparam name="t"></typeparam>
  public abstract class repositorybase<t> : irepository<t> where t : class
  {
    /// <summary>
    /// 解析模板生成静态页
    /// </summary>
    /// <param name="temppath">模板地址</param>
    /// <param name="path">静态页地址</param>
    /// <param name="t">数据模型</param>
    /// <returns></returns>
    public bool createstaticpage(string temppath, string path, t t)
    {
      try
      {
        //实例化模型
        var entity = new domain.templateview();

        //获取模板html
        string templatecontent = gethtml(temppath, system.text.encoding.utf8);
        //初始化结果
        string result = "";

        //反射赋值
        type typet = t.gettype();
        type typeen = entity.gettype();

        system.reflection.propertyinfo[] propertyinfost = typet.getproperties();

        foreach (system.reflection.propertyinfo propertyinfot in propertyinfost)
        {
          system.reflection.propertyinfo propertyinfoen = typeen.getproperty(propertyinfot.name);
          if (propertyinfoen != null && propertyinfot.getvalue(t, null) != null)
          {
            propertyinfoen.setvalue(entity, propertyinfot.getvalue(t, null), null);
          }
        }

        //很多时候 我们并没有创建复杂的主外键关系 例如栏目下的文章 我们仅仅是在文章表中添加了一个所属栏目id的字段
        //并没有创建关联 这种情况下 我们直接获取栏目的时候 是获取不到文章列表的
        //包括很多自定义的模型和字段 比如 文章的内容 可能不跟文章一个表 而是一个单独的大数据字段表 这种情况下 我们的
        //templateview.content就需要单独获取一下另一个数据模型里的 这个文章的内容 这种时候 我们可以在这里重新给他赋值

        //如 传入的模型是 文章
        //if(t is domain.articles)
        //{
        //  entity.content= 查询大数据字段表中这篇文章的内容;
          
        //}

        result = razor.parse(templatecontent, entity);

        return createfilehtmlbytemp(result, path);
      }
      catch (exception e)
      {
        throw e;
      }
    }

    /// <summary>
    /// 获取页面的html代码
    /// </summary>
    /// <param name="url">模板页面路径</param>
    /// <param name="encoding">页面编码</param>
    /// <returns></returns>
    public string gethtml(string url, system.text.encoding encoding)
    {
      byte[] buf = new webclient().downloaddata(url);
      if (encoding != null) return encoding.getstring(buf);
      string html = system.text.encoding.utf8.getstring(buf);
      encoding = getencoding(html);
      if (encoding == null || encoding == system.text.encoding.utf8) return html;
      return encoding.getstring(buf);
    }

    /// <summary>
    /// 获取页面的编码
    /// </summary>
    /// <param name="html">html源码</param>
    /// <returns></returns>
    public system.text.encoding getencoding(string html)
    {
      string pattern = @"(?i)\bcharset=(?<charset>[-a-za-z_0-9]+)";
      string charset = regex.match(html, pattern).groups["charset"].value;
      try { return system.text.encoding.getencoding(charset); }
      catch (argumentexception) { return null; }
    }

    /// <summary>
    /// 创建静态文件
    /// </summary>
    /// <param name="result">html代码</param>
    /// <param name="createpath">生成路径</param>
    /// <returns></returns>
    public bool createfilehtmlbytemp(string result, string createpath)
    {
      if (!string.isnullorempty(result))
      {
        if (string.isnullorempty(createpath))
        {
          createpath = "/default.html";
        }
        string filepath = createpath.substring(createpath.lastindexof(@"\"));
        createpath = createpath.substring(0, createpath.lastindexof(@"\"));
        if (!directory.exists(createpath))
        {
          directory.createdirectory(createpath);
        }
        createpath = createpath + filepath;
        try
        {
          filestream fs2 = new filestream(createpath, filemode.create);
          streamwriter sw = new streamwriter(fs2, new system.text.utf8encoding(false));//去除utf-8 bom
          sw.write(result);
          sw.close();
          fs2.close();
          fs2.dispose();
          return true;
        }
        catch { return false; }
      }
      return false;
    }
  }

三、我们分别创建 文章管理、公司管理、栏目管理的接口和实现类 并且他们都集成基础操作

   /// <summary>
  /// 文章管理
  /// </summary>
   public interface iarticlemanage:irepository<domain.articles>
  {
  }
  public class articlemanage:repositorybase<domain.articles>,iarticlemanage
  {
  }

  /// <summary>
  /// 公司管理
  /// </summary>
  public interface icompanymanage:irepository<domain.company>
  {
  }
  public class companymanage:repositorybase<domain.company>,icompanymanage
  {
  }

  //栏目管理
  public interface icolumnmanage:irepository<domain.column>
  {
  }
  public class columnmanage:repositorybase<domain.column>,icolumnmanage
  {
  }

四、注入xml

<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net">
 <description>spring注入service,容器指向本层层封装的接口</description>
 <object id="service.articlemanage" type="service.articlemanage,service" singleton="false">
 </object>
 <object id="service.columnmanage" type="service.columnmanage,service" singleton="false">
 </object>
 <object id="service.companymanage" type="service.companymanage,service" singleton="false">
 </object>
</objects>

五、我们分别初始化一个文章类、一个公司类(没有管理数据表,它下面没有文章列表 栏目模型我就不初始化了,怎么输出列表 大家可以参考下 栏目模板) 

  public class homecontroller : controller
  {
    /// <summary>
    /// 声明一下注入接口
    /// </summary>
    public iarticlemanage articlemanage = spring.context.support.contextregistry.getcontext().getobject("service.articlemanage") as iarticlemanage;
    public icompanymanage companymanage = spring.context.support.contextregistry.getcontext().getobject("service.companymanage") as icompanymanage;
    public icolumnmanage columnmanage = spring.context.support.contextregistry.getcontext().getobject("service.columnmanage") as icolumnmanage;


    public actionresult index()
    {
      //初始化一个文章数据模型
      var entityarticle = new domain.articles() { id = 1, title = "这里是文章标题", content = "<span style=\"color:red;\">这里是文章内容</span>", author = "张三", createdate = datetime.now };

      //初始化一个公司数据模型
      var entitycompany = new domain.company() { id = 1, companyname = "这里是公司名称", companytel = "公司电话", contectuser = "张三", createdate = datetime.now };

      //调用方法生成静态页面
      articlemanage.createstaticpage(server.mappath("/templates/temp_article.html"), server.mappath("/pages/news/" + datetime.now.tostring("yyyymmddhhmmss") + "1.html"), entityarticle);
      companymanage.createstaticpage(server.mappath("/templates/temp_company.html"), server.mappath("/pages/news/" + datetime.now.tostring("yyyymmddhhmmss") + "2.html"), entitycompany);

      return view();
    }

    public actionresult about()
    {
      viewbag.message = "your application description page.";

      return view();
    }

    public actionresult contact()
    {
      viewbag.message = "your contact page.";

      return view();
    }
    
  }

 六、这是测试的简单的文章模板、公司模板和栏目模板 

详解ASP.NET MVC 解析模板生成静态页(RazorEngine)

<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  <title>@model.title</title>
</head>
<body>
  <h1>@model.title</h1>
  <p>作者:@model.author - 发布时间:@model.createdate</p>
  <p>@raw(model.content)</p>
</body>
</html>
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
  <title></title>
</head>
<body>
  <p>公司名称:@model.companyname</p>
  <p>公司电话:@model.companytel</p>
  <p>联系人:@model.contectuser</p>
  <p>创建时间:@model.createdate</p>
</body>
</html>
 
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
  <title></title>
</head>
<body>
  <p>栏目标题: @model.title</p>
  <p>
    文章列表
    <ul>
      @foreach(var item in @model.articles)
      {
      <li>
        <a href="">
          <span>@item.title</span>
          <span>@item.author</span>
          <span>@item.createdate</span>
        </a>
      </li>
      }
    </ul>
  </p>
</body>
</html>

我们运行一下,大功告成~~~

 

详解ASP.NET MVC 解析模板生成静态页(RazorEngine)

详解ASP.NET MVC 解析模板生成静态页(RazorEngine) 

详解ASP.NET MVC 解析模板生成静态页(RazorEngine)

怎么排序?怎么获取前几条?怎么格式化日期时间?怎么分页?

这可是razor啊,这都不需要再多讲了吧,简单一说,如果你传入数据前没有事先排序或者获取前几条,这些操作要做模板里操作 那跟在.cshtml里基本是一样的

@foreach(var item in @model.listcolumn)
{

 <div >
@if (@item.linkurl==null)
  {
    <ul>
@foreach(var article in @item.com_article.take(15).orderbydescending(p=>p.updatedate))
{

<li>
      <a href="@article.linkurl" rel="external nofollow" class="gd-a">
        <div>@article.title</div></a>
      </li>
}
 </ul>
   }
  else
   {

  }
</div>
}

 应用还是很广泛的,而且解析代码相对于标签替换来说十分简洁、高效。有时间可以多研究研究,改天有空写一个模板替换标签的供大家参考一下。有人会说那我还得教前台制作razor语法,这种说法我们没法去置评,标签替换你仍然要教他如何使用标签啊,所以是不是复杂并不是探究的主题,想要前台制作人员更方便的制作一套模板语法并不是主要因素,比如我们可以做一套方便的模板制作,用户点击一下就生成代码,或者直接做成可视化的,这可能让我们的程序员要耗费更多的精力,但是一劳永逸,标签替换方式你仍然要给前台制作人员一套标签规范和语法,况且后台解析异常的庞大和复杂。

详解ASP.NET MVC 解析模板生成静态页(RazorEngine) 

 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。