Skip to content

Commit

Permalink
#31 Added more documentation for Validation and Startup.cs
Browse files Browse the repository at this point in the history
#25 Cleaned up the .nuget project. It should reflect the source project more accurately now.
  • Loading branch information
hha046 committed Jul 3, 2019
1 parent cb1c8d0 commit a8542e5
Show file tree
Hide file tree
Showing 9 changed files with 256 additions and 41 deletions.
2 changes: 1 addition & 1 deletion docs/FHIRService/CreatingAFHIRService.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ That's it.

## If you want validation

You'll need profiles to validate from to make it properly work. However you can use the standard profile to initally test and see if the validation works.
You'll need profiles defined as [StructureDefintions](https://www.hl7.org/fhir/structuredefinition.html) to validate from to make it properly work. However you can use the standard profile to initally test and see if the validation works.
Go to [Getting started with Validation](../Validation/GettingStartedWithValidation.md) to learn more.
25 changes: 11 additions & 14 deletions docs/FHIRService/SettingUpAResourceService.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
# Some quick pointers in order to successfully create a FHIR Resource service

FhirStarter is built around the [.NET API for HL7 FHIR](https://github.com/FirelyTeam/fhir-net-api) and it's conventions.
FhirStarter is built around the [.NET API for HL7 FHIR](https://github.com/FirelyTeam/fhir-net-api) and its conventions.
A [Resource](https://www.hl7.org/fhir/resource.html) service in FhirStarter is based on classes that inherits the [Base](https://github.com/FirelyTeam/fhir-net-api/blob/develop-stu3/src/Hl7.Fhir.Core/Model/Base.cs) model.

Many endpoints (Create, Read, Update, etc .. ) looks into the [SearchParam](https://github.com/FirelyTeam/fhir-net-api/blob/develop-stu3/src/Hl7.Fhir.Core/Rest/SearchParams.cs) object to figure out what is being queried for.
The SearchParams object will only accept valid property names in the request url.


There are three possible outcomes from a FHIR Service:
- A Resource which is either a Resource or a [Bundle](https://www.hl7.org/fhir/bundle.html)
- A validation report if validation is enabled and the input or output doesn't match the profile.
- A xml or json result which is either a Resource or a [Bundle](https://www.hl7.org/fhir/bundle.html)
- An [OperationOutcome](https://www.hl7.org/fhir/operationoutcome.html) if an exception occurs.
- A validation report in form of a OperationOutcome if validation is enabled and the input or output doesn't match the profile.
- Input is typically create, patch or update
- Output is the response returned to the client from the server
- An [OperationOutcome](https://www.hl7.org/fhir/operationoutcome.html) if an exception occurs.


You'll need to handle and test for these three scenarios.

## Setting up a Resource service

### The IFhirService

To create a new Resource service, just create a new file in your web application. Initially it can typically look something like this:
To create a new Resource service with the IFhirService interface, just create a new file in your web application. Initially it can typically look something like this:

```c#
using System;
Expand Down Expand Up @@ -81,7 +81,7 @@ namespace PatientAdministration.Person.R4.Services
throw new NotImplementedException();
}

public ICollection<string> GetStructureDefinitionNames()
public string GetStructureDefinitionNameForResourceProfile()
{
throw new NotImplementedException();
}
Expand Down Expand Up @@ -266,14 +266,11 @@ Here you have only one parameter and that's the identifier of your FHIR resource
Unlike Update, Patch will let you update part of a resource instead of the whole resource. F.ex if you want to update a field in a resource or your datasource.

##### Key - todo check this part out
The key is handled by the ServiceHandler
-- TODO

#### StructureDefinition

##### GetStructureDefinitionNames

You can add your custom StructureDefinition(s) to the service, by first adding the names of the StructureDefinitions (see the ExamplePatientService for a quick howto).
#### StructureDefinition

Additionally you need to implement IFhirStructureDefinitionService so the FhirController can retrieve the source of the StructureDefinition. You can choose where to get the definition from, but you have to return the result as an ICollection.
##### GetStructureDefinitionNameForResourceProfile

The StructureDefinitions are available through the url .../fhir/StructureDefinition/
You can add your custom StructureDefinition to the service, by adding the name of the StructureDefinition (see the [Getting started with Validation](../Validation/GettingStartedWithValidation.md) for a quick howto).
139 changes: 139 additions & 0 deletions docs/FHIRService/SettingUpStartup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Setting up Startup.cs

For the FhirStarter to work with all of the necessary dependencies, Startup.cs has to be configured.

For the development version, look at [Startup.cs](https://github.com/verzada/FhirStarter.DotNetCore/blob/master/src/FhirStarter.R4.Twisted.Core/Startup.cs). <br />
For the nuget setup version, look at [Startup.cs](https://github.com/verzada/FhirStarter.DotNetCore/blob/master/src/FhirStarter.R4.Twisted.Core.Nuget/Startup.cs).

## What needs to be added in the IServiceCollection

### FhirSetup

ConfigureServices runs a customized method to which adds config files, necessary dlls and ouputs for the web project.
The dlls, detonator and instigator needs to be accessible in memory, .Net Core handles from our experience, dlls differently from .Net Framework, so the dll's are are added at startup.

#### FhirStarterSettings
The FhirStarterSettings needs to be accessible for the FhirController, so the dll(s) containing the IFhirService(s) can be known to the service and added to memory.

#### Formats
By default we recommend removing the standard output formats and add the necessary formats afterwards.

#### The Fhir Controller

The Fhir Controller is in the Instigator library and must be added as an AddApplicationPart as well as AddControllerAsServices() in order for the FhirController to work. If you want to ovveride the FhirStarter controller with a your custom controller, it is possible to do it here, by f.ex. adding the controller directory into the web project and ignoring the AddControllerAsServices call.

### Code example for FhirSetup used by

```c#
private void FhirSetup(IServiceCollection services)
{
var appSettings =
StartupConfigHelper.BuildConfigurationFromJson(AppContext.BaseDirectory, "appsettings.json");
FhirStarterConfig.SetupFhir(services, appSettings, CompatibilityVersion.Version_2_2);

var detonator = FhirStarterConfig.GetDetonatorAssembly();
var instigator = FhirStarterConfig.GetInstigatorAssembly();

services.Configure<FhirStarterSettings>(appSettings.GetSection(nameof(FhirStarterSettings)));
services.AddMvc(options =>
{
options.OutputFormatters.Clear();
options.RespectBrowserAcceptHeader = true;
options.OutputFormatters.Add(new XmlFhirSerializerOutputFormatter());
options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
options.OutputFormatters.Add(new JsonFhirFormatter());

})
.AddJsonOptions(options =>
{
options.SerializerSettings.Formatting = Formatting.Indented;
})
.AddApplicationPart(instigator).AddApplicationPart(detonator).AddControllersAsServices()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
```

## What needs to be added in the Configure Section

### ExceptionHandling

In .Net Core, the best way of customizing the exception handling, seems to be in the Configure method.
The solution we've found so far, which gives the developer a chance of changing the ouput from the exception into an OperationOutcome, is to configure it in the Startup.cs file.

We've chosen to serialize the response to json by default, but it is possible to change it to what is necessary for any given scenario.

### Logging

We've chosen to use log4net since it's easier and quicker to configure.
In order to configure log4net sucessfully, make sure the log4net.config file is accessible from the root folder of the web project and that the Main method in the Program.cs file has the line

```c#
.ConfigureLogging((hostingContext, logging) => { logging.AddLog4Net("log4net.config"); })
```

The log4net.config file is customizable to any extent. In our version of config file, info and warn/errors are logged to two different files. It is possible to just have one file for the same purpose. For more information about log4net look at the [guide from apache](https://logging.apache.org/log4net/release/manual/configuration.html)

``` xml
<?xml version="1.0" encoding="utf-8" ?>
<log4net>
<appender name="LogFileAppenderInfo" type="log4net.Appender.FileAppender">
<param name="File" value="c:\logs\FhirStarter.STU3.Twisted.Core_info.log" />
<param name="AppendToFile" value="true" />
<layout type="log4net.Layout.PatternLayout">
<param name="Header" value="" />
<param name="Footer" value="" />
<param name="ConversionPattern" value="%d [%t] %-5p %m%n" />
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<levelMin value="INFO"/>
<levelMax value="INFO" />
</filter>
</appender>
<appender name="LogFileAppenderWarn" type="log4net.Appender.FileAppender">
<param name="File" value="c:\logs\FhirStarter.STU3.Twisted.Core_warn.log" />
<param name="AppendToFile" value="true" />
<layout type="log4net.Layout.PatternLayout">
<param name="Header" value="" />
<param name="Footer" value="" />
<param name="ConversionPattern" value="%d [%t] %-5p %m%n" />
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<levelMin value="WARN"/>
</filter>
</appender>
<root>
<appender-ref ref="LogFileAppenderInfo" />
<appender-ref ref="LogFileAppenderWarn" />
</root>
</log4net>
```

### Code Example for Configure

```c#
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILogger<IFhirService> logger)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.UseExceptionHandler(a => a.Run(async context =>
{
var feature = context.Features.Get<IExceptionHandlerPathFeature>();
if (feature?.Error != null)
{
var exception = feature.Error;

var operationOutcome = ErrorHandlingMiddleware.GetOperationOutCome(exception, true);
var fhirJsonConverter = new FhirJsonSerializer();
var result = fhirJsonConverter.SerializeToString(operationOutcome);
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(result);
}
}));

app.ConfigureExceptionHandler(logger);
app.UseMvc();
}
```
76 changes: 74 additions & 2 deletions docs/Validation/GettingStartedWithValidation.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,76 @@
# Getting started with validation in FhirStarter.DotNetCore

Sorry we're in the middle of creating the documentation.
Take a look around in the source code if you are impatient (we would be).
In this section we'll describe how to get the validation running with a [StructureDefintion](https://www.hl7.org/fhir/structuredefinition.html).
FhirStarter assumes that the StructureDefinition is per [Resource](https://www.hl7.org/fhir/resource.html) type and therefore the name of each StructureDefinition must be unique in the Service inheriting IFhirService.

## What a StructureDefinition looks like

A simple StructureDefinition wihtout any sort of requirements other than the type [Patient](https://www.hl7.org/fhir/patient.html):
```xml
<?xml version="1.0" encoding="utf-8"?>
<StructureDefinition xmlns="https://hl7.org/fhir">
<url value="https://github.com/verzada/FhirStarter.DotNetCore/fhir/StructureDefinition/FhirStarterPatient" />
<name value="FhirStarterPatient" />
<status value="draft" />
<fhirVersion value="4.0.0" />
<kind value="resource" />
<abstract value="false" />
<type value="Patient" />
<baseDefinition value="https://hl7.org/fhir/StructureDefinition/Patient" />
<derivation value="constraint" />
</StructureDefinition>
```

A more detailed StructureDefinition for a [Person](https://www.hl7.org/fhir/person.html) service:

```xml
<StructureDefinition xmlns="https://hl7.org/fhir">
<url value="https://github.com/verzada/FhirStarter.DotNetCore/fhir/StructureDefinition/FhirStarterPerson"/>
<name value="FhirStarterPerson"/>
<status value="draft"/>
<fhirVersion value="4.0.0"/>
<kind value="resource"/>
<abstract value="false"/>
<type value="Person"/>
<baseDefinition value="https://hl7.org/fhir/StructureDefinition/Person"/>
<derivation value="constraint"/>
<differential>
<element id="Person.telecom">
<path value="Person.telecom"/>
<max value="0"/>
</element>
<element id="Person.gender">
<path value="Person.gender"/>
<min value="1"/>
</element>
<element id="Person.birthDate">
<path value="Person.birthDate"/>
<min value="1"/>
</element>
<element id="Person.photo">
<path value="Person.photo"/>
<max value="0"/>
</element>
<element id="Person.managingOrganization">
<path value="Person.managingOrganization"/>
<max value="0"/>
</element>
<element id="Person.link">
<path value="Person.link"/>
<max value="0"/>
</element>
</differential>
</StructureDefinition>
```

## Where the StructureDefinition is located

The StructureDefinition is located in any web project exposing a FHIR R4 Service.
For the StructureDefinition to be picked up by the FhirStarter controller, the definitions must be placed in a specific folder structure in the web project:

- Root folder
- Resources
- StructureDefinitions

The resources available in the folder structure must be set to **Copy if newer** in the *Copy to Output Directory* option for each Structure Definition file.

10 changes: 0 additions & 10 deletions src/FhirStarter.R4.Twisted.Core.Nuget/FhirStarterSettings.json

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,9 @@ public OperationDefinition GetOperationDefinition(HttpRequest request)
return defintion;
}

public ICollection<string> GetStructureDefinitionNames()
public string GetStructureDefinitionNameForResourceProfile()
{
return new List<string> {GetServiceResourceReference()};
return "FhirStarterPatient";
}

private static Base MockPatient()
Expand Down
25 changes: 18 additions & 7 deletions src/FhirStarter.R4.Twisted.Core.Nuget/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
using System;
using FhirStarter.R4.Detonator.Core.Filter;
using FhirStarter.R4.Detonator.Core.Formatters;
using FhirStarter.R4.Detonator.Core.Interface;
using FhirStarter.R4.Instigator.Core.Configuration;
using FhirStarter.R4.Instigator.Core.Extension;
using FhirStarter.R4.Instigator.Core.Helper;
using FhirStarter.R4.Instigator.Core.Model;
using Hl7.Fhir.Serialization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace FhirStarter.R4.Twisted.Core
{
Expand All @@ -35,7 +39,7 @@ public void ConfigureServices(IServiceCollection services)
private void FhirSetup(IServiceCollection services)
{
var fhirStarterSettings =
StartupConfigHelper.BuildConfigurationFromJson(AppContext.BaseDirectory, "FhirStarterSettings.json");
StartupConfigHelper.BuildConfigurationFromJson(AppContext.BaseDirectory, "appsettings.json");
FhirStarterConfig.SetupFhir(services, fhirStarterSettings, CompatibilityVersion.Version_2_2);

var detonator = FhirStarterConfig.GetDetonatorAssembly();
Expand All @@ -50,6 +54,10 @@ private void FhirSetup(IServiceCollection services)
options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
options.OutputFormatters.Add(new JsonFhirFormatter());
})
.AddJsonOptions(options =>
{
options.SerializerSettings.Formatting = Formatting.Indented;
})
.AddApplicationPart(instigator).AddApplicationPart(detonator).AddControllersAsServices()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
Expand All @@ -63,18 +71,21 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILogger<
app.UseDeveloperExceptionPage();
}

//app.UseMiddleware<HeaderValidation>();
// To get OperationOutcome add this feature
app.UseExceptionHandler(a => a.Run(async context =>
{
var feature = context.Features.Get<IExceptionHandlerPathFeature>();
var exception = feature.Error;
// var operationOutcome = FhirStarter.R4.Detonator.Core.Fiter.ErrorHandlingMiddleware.
if (feature?.Error != null)
{
var exception = feature.Error;
var operationOutcome = ErrorHandlingMiddleware.GetOperationOutCome(exception, true);
var fhirJsonConverter = new FhirJsonSerializer();
var result = fhirJsonConverter.SerializeToString(operationOutcome);
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(result);
}
}));


app.ConfigureExceptionHandler(logger);
app.UseMvc();
}
Expand Down
9 changes: 8 additions & 1 deletion src/FhirStarter.R4.Twisted.Core.Nuget/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,12 @@
"Default": "Warning"
}
},
"AllowedHosts": "*"
"AllowedHosts": "*",
"FhirStarterSettings": {
"FhirServiceAssemblies": [ "FhirStarter.R4.Twisted.Core.Nuget" ],
"MockupEnabled": "false",
"EnableValidation": "false",
"LogRequestWhenError": "false",
"FhirPublisher": "ACME"
}
}
Loading

0 comments on commit a8542e5

Please sign in to comment.