How to implement WADO-URI Middleware in DICOM Web Server
In DicomObjects .NET 8 version, users can define their own self-hosted server start up and use the middleware they design.
An example is where the server wish to route legacy WADO-URI requests to the correct WADO controlers.
Sample custom Startup class:
public class DicomWebStartup : DicomObjects.DicomWeb.IStartup
{
public void Configure(IApplicationBuilder app)
{
// 1. Intercept legacy WADO-URI requests
app.Use(async (context, next) =>
{
var path = context.Request.Path.Value?.ToLowerInvariant();
if (path == "/wado" &&
context.Request.Query.TryGetValue("requestType", out var requestType) &&
string.Equals(requestType, "WADO", StringComparison.OrdinalIgnoreCase))
{
// extract parameters
var studyUid = context.Request.Query["studyUID"].ToString();
var seriesUid = context.Request.Query["seriesUID"].ToString();
var objectUid = context.Request.Query["objectUID"].ToString();
var frameNumber = context.Request.Query["frameNumber"].ToString().Trim();
if (string.IsNullOrEmpty(frameNumber)) frameNumber = "1";
var contentType = context.Request.Query["contentType"].ToString().Trim();
var rows = context.Request.Query["rows"].ToString().Trim();
var columns = context.Request.Query["columns"].ToString().Trim();
// basic validation
if (string.IsNullOrWhiteSpace(studyUid) ||
string.IsNullOrWhiteSpace(seriesUid) ||
string.IsNullOrWhiteSpace(objectUid))
{
context.Response.StatusCode = 400;
await context.Response.WriteAsync("Missing required UIDs");
return;
}
// build internal route for WADO-RS
var newPath = $"/wado/studies/{studyUid}/series/{seriesUid}/instances/{objectUid}";
newPath += $"/frames/{frameNumber}";
// rewrite path and set Accept header
if (!string.IsNullOrEmpty(rows) && !string.IsNullOrEmpty(columns)) // assuming thumbnail
{
context.Request.Path = newPath + "/thumbnail"; // /thumbnail?viewport=rows,columns will not hit the controller, use querystring instead
context.Request.QueryString =
new QueryString($"?viewport={rows},{columns}");
}
else
context.Request.Path = newPath + "/rendered"; // append /rendered so it tells DicomObjects what the wado operation this is
string path1 = context.Request.Path.Value;
if (!string.IsNullOrEmpty(contentType))
context.Request.Headers["Accept"] = contentType;
else
context.Request.Headers["Accept"] = "image/jpeg"; // default to image/jpeg if this is what the client expects to receive in response
// this then calls the DicomWebServer's Wado Controller and will fire your WadoReceived event handler
await next.Invoke();
return;
}
// Otherwise continue normal pipeline
await next.Invoke();
});
// 2. Normal ASP.NET Core pipeline (routing, controllers, etc.)
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
/// <summary>
/// Configure all required services
/// </summary>
/// <param name="services">The IService collection</param>
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers().AddControllersAsServices();
}
}Sample user code:
Serer Side
const string serverURL = "http://localhost:9090";
DicomWebServer server = new();
Action<ILoggingBuilder> loggingProvider = loggingBuilder =>
{
loggingBuilder.ClearProviders();
loggingBuilder.AddConsole();
loggingBuilder.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Error);
};
// use custom web server start up in the Listen method
_ = server.Listen(serverURL, "", "", false, new DicomWebStartup(), loggingProvider);
_ = server.Listen(asyncServerURL, "", "", true, new DicomWebStartup(), loggingProvider);
// set up DicomWebServer's event handler for WADO
server.WadoReceived += Server_WadoReceived;Client Side
Use postman or any http client, and construct a WADO-URI request similar to the following
http://localhost:9090/wado?requestType=WADO&studyUID={studyUID}&seriesUID={seriesUID}&objectUID={instanceUID}&rows=128&columns=128