.
Anmeldung | Registrieren | Hilfe

.NET-Blogs Archiv Februar 2017

Creating a Word document OutputFormatter in ASP.NET Core

22.02.2017 17:00:00 | Jürgen Gutsch

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.

BrowserLink causes an error in a new ASP.NET Core web

09.02.2017 17:00:00 | Jürgen Gutsch

Using the latest bits of Visual Studio 2017 and the latest SDK of .NET Core, you will possibly get an error while starting your ASP.NET Core Web in Visual Studio 2017 or using dotnet run.

In Visual Studio 2017 the browser opens on pressing F5, but with wired HTML code in the address bar. Debugging starts and stops a few seconds later. Using the console you'l get a more meaningful error message:

This error means, something is missing the system.runtime.dll ("System.Runtime, Version=4.2.0.0") which is not referenced or used somewhere directly. I had a deeper look into the NuGet references and couldn't found it.

Because I often had problems with BrowserLink in the past, I removed the NuGet reference from the project and all worked fine. I added it again, to be sure that the removal didn't clean up anything. The error happened again.

It seams that the current Version of BrowserLink is referencing a library which is not supported by the app. Remove it, if you get the same or a similar error.

BrowserLink in general is a pretty cool feature, it refreshes the browser magically if anything changes on the server. With this tool, you are able to edit your CSS files and preview it directly in the browser without doing a manual refresh. It is a VisualStudio Add-in and uses NuGet-Packages to extend your app to support it.

Using Dependency Injection in .NET Core Console Apps

07.02.2017 17:00:00 | Jürgen Gutsch

The Dependency Injection (DI) Container used in ASP.NET Core is not limited to ASP.NET Core. You are able to use it in any kind of .NET Project. This post shows how to use it in an .NET Core Console application.

Create a Console Application using the dotnet CLI or Visual Studio 2017. The DI Container is not available by default, bit the IServiceProvider is. If you want to use an Custom or third party DI Container, you should provide an implementation if an IServiceProvider, as an encapsulation of a DI Container.

In this post I want to use the DI Container used in the ASP.NET Core projects. This needs an additional NuGet package "Microsoft.Extensions.DependencyInjection" (currently it is version 1.1.0)

Since this library is a .NET Standard Library, it should also work in a .NET 4.6 application. You just need to add a reference to "Microsoft.Extensions.DependencyInjection"

After adding that package we can start to use it. I created two simple classes which are dependent to each other, to show the how it works in a simple way:

public class Service1 : IDisposable
{
  private readonly Service2 _child;
  public Service1(Service2 child)
  {
    Console.WriteLine("Constructor Service1");
    _child = child;
  }

  public void Dispose()
  {
    Console.WriteLine("Dispose Service1");
    _child.Dispose();
  }
}

public class Service2 : IDisposable
{
  public Service2()
  {
    Console.WriteLine("Constructor Service2");
  }

  public void Dispose()
  {
    Console.WriteLine("Dispose Service2");
  }
}

Usually you would also use interfaces and create the relationship between this two classes, instead of the concrete implementation. Anyway, we just want to test if it works.

In the static void Main of the console app, we create a new ServiceCollection and register the classes in a transient scope:

var services = new ServiceCollection();
services.AddTransient<Service2>();
services.AddTransient<Service1>();

This ServiceCollection comes from the added NuGet package. Your favorite DI container possibly uses another way to register the services. You could now share the ServiceCollection to additional components, who wants to share some more services, in the same way ASP.NET Core does it with the AddSomething (e. g. AddMvc()) extension methods.

Now we need to create the ServiceContainer out of that collection:

var provider = services.BuildServiceProvider();

We can also share the ServiceProvider in our application to retrieve the services, but the proper way is to use it only on a single entry point:

using (var service1 = provider.GetService<Service1>())
{
  // so something with the class
}

Now, let's start the console app and look at the console output:

As you can see, this DI container is working in any .NET Core app.

Regeln | Impressum