In one of the ASP.NET Core projects we did in the last year, we created an OutputFormatter to provide a Word documents as printable reports via ASP.NET Core Web API. Well, this formatter wasn't done by me, but done by a fellow software developer Jakob Wolf at the yooapps.com. I told him to write about it, but he hadn't enough time to do it yet, so I'm going to do it for him. Maybe you know about him on Twitter. Maybe not, but he is one of the best ASP.NET and Angular developers I ever met.

About OutputFormatters

In ASP.NET you are able to have many different formatters. The best known built-in formatter is the JsonOutputFormatter which is used as the default OutputFormatter in ASP.NET Web API.

By using the AddMvcOptions() you are able to add new Formatters or to manage the existing formatters:

services.AddMvc()
.AddMvcOptions(options =>
{
options.OutputFormatters.Add(new WordOutputFormatter());
options.FormatterMappings.SetMediaTypeMappingForFormat(
"docx", MediaTypeHeaderValue.Parse("application/ms-word"));
})

As you can see in the snippet above, we add the Word document formatter (called WordOutputFormatter to provide the Word documents if the requested type is "application/ms-word".

You are able to add whatever formatter you need, provided on whatever media type you want to support.

Let's have a look how a output formatter looks like:

public class MyFormatter : IOutputFormatter
{
public bool CanWriteResult(OutputFormatterCanWriteContext context)
{
// check whether to write or not
throw new NotImplementedException();
} public async Task WriteAsync(OutputFormatterWriteContext context)
{
// write the formatted contents to the response stream.
throw new NotImplementedException();
}
}

You have one method to check whether the data can be written to the expected format or not. The other async method does the job to format and output the data to the response stream, which comes with the context.

This way needs to do some things manually. A more comfortable way to implement an OutputFormatter is to inherit from the OutputFormatter base class directly:

public class WordOutputFormatter : OutputFormatter
{
public string ContentType { get; } public WordOutputFormatter()
{
ContentType = "application/ms-word";
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(ContentType));
} // optional, but makes sense to restrict to a specific condition
protected override bool CanWriteType(Type type)
{
if (type == null)
{
throw new ArgumentNullException(nameof(type));
} // only one ViewModel type is allowed
return type == typeof(DocumentContentsViewModel);
} // this needs to be overwritten
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
{
// Format and write the document outputs here
throw new NotImplementedException();
}
}

The base class does some things for you. For example to write the correct HTTP headers.

Creating Word documents

To create Word documents you need to add a reference to the Open XML SDK. We used the OpenXMLSDK-MOT with the version 2.6.0, which cannot used with .NET Core. This is why we run that specific ASP.NET Core project on .NET 4.6.

Version 2.7.0 is available as a .NET Standard 1.3 library and can be used in .NET Core. Unfortunately this version isn't yet available in the default NuGet Feed. To install the latest Version, follow the instructions on GitHub: https://github.com/officedev/open-xml-sd Currently there is a mess with the NuGet package IDs and versions on NuGet and MyGet. Use the MyGet feed, mentioned on the GitHub page to install the latest version. The package ID here is DocumentFormat.OpenXml and the latest stable Version is 2.7.1

In this post, I don't want to go threw all the word processing stuff , because it is too specific to our implementation. I just show you how it works in general. The Open XML SDK is pretty well documented, so you can use this as an entry point to create your own specific WordOutputFormatter:

public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
{
var response = context.HttpContext.Response;
var filePath = Path.GetTempFileName(); var viewModel = context.Object as DocumentContentsViewModel;
if (viewModel == null)
{
throw new ArgumentNullException(nameof(viewModel));
} using (var wordprocessingDocument = WordprocessingDocument
.Create(filePath, WordprocessingDocumentType.Document))
{
// start creating the documents and the main parts of it
wordprocessingDocument.AddMainDocumentPart(); var styleDefinitionPart = wordprocessingDocument.MainDocumentPart
.AddNewPart<StyleDefinitionsPart>();
var styles = new Styles();
styles.Save(styleDefinitionPart); wordprocessingDocument.MainDocumentPart.Document = new Document
{
Body = new Body()
};
var body = wordprocessingDocument.MainDocumentPart.Document.Body; // call a helper method to set default styles
AddStyles(styleDefinitionPart);
// call a helper method set the document to landscape mode
SetLandscape(body); foreach (var institution in viewModel.Items)
{
// iterate threw some data of the viewmodel
// and create the elements you need // ... more word processing stuff here } await response.SendFileAsync(filePath);
}
}

The VewModel with the data to format, is in the Object property of the OutputFormatterWriteContext. We do a save cast and check for null before we continue. The Open XML SDK works based on files. This is why we need to create a temp file name and let the SDK use this file path. Because of that fact - at the end - we send the file out to the response stream using the response.SendFileAsync() method. I personally prefer to work on the OutputStream directly, to have less file operations and to be a little bit faster. The other thing is, we need to cleanup the temp files.

After the file is created, we work on this file and create the document, custom styles and layouts and the document body, which will contain the formatted data. Inside the loop we are only working on that Body object. We created helper methods to add formatted values, tables and so on...

Conclusion

OutputFormatters are pretty useful to create almost any kind of content out of any kind of data. Instead of hacking around in the specific Web API actions, you should always use the OutputFormatters to have reusable components.

The OutputFormatter we build, is not really reusable or even generic, because it was created for a specific kind of a report. But with this starting point, we are able to make it generic. We could pass a template document to the formatter, which knows the properties of the ViewModel, this way it is possible to create almost all kind of Word documents.

Asp.Net Core 输出 Word的更多相关文章

  1. asp.net core输出中文乱码的问题

    摘要 在学习asp.net core的时候,尝试在控制台,或者页面上输出中文,会出现乱码的问题. 问题重现 新建控制台和站点 public class Program { public static ...

  2. .NET CORE 2.0小白笔记(四):asp.net core输出中文乱码的问题

    问题描述:在学习asp.net core的时候,尝试在控制台,或者页面上输出中文,会出现乱码的问题. 分析解决:控制台乱码的原因是因为中文windows命令行默认编码页是gb2312,想输出中文只要把 ...

  3. ASP.NET Core MVC压缩样式、脚本及总是复制文件到输出目录

    前言 在.NET Core之前对于压缩样式文件和脚本我们可能需要借助第三方工具来进行压缩,但在ASP.NET MVC Core中则无需借助第三方工具来完成,本节我们来看看ASP.NET Core MV ...

  4. 使Asp.net Core同时支持输出Json/Xml

    我们知道Asp.net Core是支持输出为Json格式的.同时也支持输出为xml格式.只要我们正确的配置.并在Request时指定正确的Accept,即可根据不同的Header来输出不同的格式. 前 ...

  5. ASP.NET Core 2.1 : 十二.内置日志、使用Nlog将日志输出到文件

    应用离不开日志,虽然现在使用VS有强大的调试功能,开发过程中不复杂的情况懒得输出日志了(想起print和echo的有木有),但在一些复杂的过程中以及应用日常运行中的日志还是非常有用. ASP.NET ...

  6. ASP.NET Core 集成测试中通过 Serilog 向控制台输出日志

    日志是程序员的雷达,不仅在生产环境中需要,在集成测试环境中也需要,可以在持续集成失败后帮助定位问题.与生产环境不同,在集成测试环境中使用控制台输出日志更方便,这样可以通过持续集成 runner 执行 ...

  7. ASP.NET Core MVC中的IActionFilter.OnActionExecuted方法执行时,Controller中Action返回的对象是否已经输出到Http Response中

    我们在ASP.NET Core MVC项目中有如下HomeController: using Microsoft.AspNetCore.Mvc; namespace AspNetCoreActionF ...

  8. ASP.NET Core MVC中Controller的Action如何直接使用Response.Body的Stream流输出数据

    在ASP.NET Core MVC中,我们有时候需要在Controller的Action中直接输出数据到Response.Body这个Stream流中,例如如果我们要输出一个很大的文件到客户端浏览器让 ...

  9. ASP.NET Core MVC的Razor视图中,使用Html.Raw方法输出原生的html

    我们在ASP.NET Core MVC项目中,有一个Razor视图文件Index.cshtml,如下: @{ Layout = null; } <!DOCTYPE html> <ht ...

随机推荐

  1. matlab中fix函数,floor函数,ceil函数

    1)fix(n)的意义是取小于n的整数(是向零点舍入的意思是往零的方向上靠),这是一类应用在整数取值上的函数,就如同以前我们所研究的求整问题,例如,fix(pi)=3;fix(3.5)=3;fix(- ...

  2. 环境配置 jupyter代码自动补全

    自动补全 参考链接: https://www.lefer.cn/posts/15473/

  3. Mybatis学习总结(八)——延迟加载

    一.什么是延迟加载 resultMap可以实现高级映射(使用association.collection实现一对一及一对多映射),association.collection具备延迟加载功能. 需求: ...

  4. Java发送Email邮件及SpringBoot集成

    一:普通方式发送 1.导包 <!--Java MAil 发送邮件API--> <dependency> <groupId>javax.mail</groupI ...

  5. SpringBoot整合篇

    目录 SpringBoot整合篇 SpringBoot简介 SpringBoot运行 SpringBoot目录结构 整合JdbcTemplate @RestController 整合JSP 整合JPA ...

  6. git仓库迁移

    最近,装了git的本地服务器坏掉了, 没办法只能临时进行仓库的迁移  保证项目正常进行 在项目的根目录执行右键执行 查询当前仓库的远程地址 git remote -v 查看现有远程仓库的地址url 修 ...

  7. Arduino Core For ESP8266

    如果选择纯C作为ESP8266的开发,有两个途径: 使用乐鑫官方原生的 RTOS-SDK或者NONOS-SDK 使用Arduino IDE 使用PlatformIO 作为一个"Arduino ...

  8. (通用版)salesforce中soql及sosl的伪‘Like’模糊检索

    salesforce里有soql.sosl两种查询语法,soql针对模糊搜索也有‘like’关键字,然而只能针对其自带字段如:Name.Id:对于自定义添加的字段如:Message__c.Note__ ...

  9. Beta阶段团队成员贡献分分配规则

    Beta阶段团队成员贡献分分配规则 Alpha阶段贡献分分配有些负责,在这里进行一些修改: 对任务完成得分部分进行了简化 对发现bug的惩罚措施进行了修改 移除了优化得分,不鼓励修改他人代码 移除了帮 ...

  10. Python 内置库 sys用法

    sys模块功能众多,这边先学习几个常用的方法sys常见函数列表① sys.argv: 实现从程序外部向程序传递参数.其实sys.argv[]就是一个列表,里面的项为用户输入的参数,但是sys.argv ...