I have a rather complex WebAPI2
project using Owin
and Dependency Injection using Unity
. I now do have a controller action that gets MultipartContent
from the Request.Content
that is then handed over to a service (created by DI) that temporarily stores this file to the hard drive of the server. This is needed because the service uses an external and native DLL that reads this file for some validation and conversion. So the external DLL additionally stores the converted file as well as the conversion result (as XML) in the same location. My service now reads the conversion result and returns that result. After that the temp files are deleted.
Problem now is that the Dispose()
method of my own IDependencyResolver
is called twice during that operation which actually destroys stuff so that the next request also fails. For example I do get errors that the controller cannot be created as it has no parameterless constructor or an ObjectDisposedException
on System.Web.Http.HttpServer
. After that the DI container is properly rebuild and everything works fine. And so on. So every second request fails.
So here is some code:
Owin Startup
[assembly: OwinStartup(typeof(MyNamespace.Startup))]
namespace myNamespace
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
HttpConfiguration httpConfiguration = new HttpConfiguration();
httpConfiguration.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
app.UseCors(CorsOptions.AllowAll);
IUnityContainer container = new UnityContainer();
// setup dependency injection
container.RegisterType<IFileConversionService, FileConversionService>();
UnityConfig.RegisterControllers(container);
// register routes
WebApiConfig.Register(httpConfiguration);
// setup dependency resolver
httpConfiguration.DependencyResolver = new MyNamespace.UnityResolver(container);
app.UseWebApi(httpConfiguration);
}
}
}
Well I don't think that there is anything special about that. As you can see I do not use the GlobalConfiguration.Configuration.DependencyResolver
due to Owin
.
My DependencyResolver:
public class UnityResolver : IDependencyResolver
{
private IUnityContainer _container;
private bool _disposed;
public UnityResolver(IUnityContainer container)
{
if (container == null)
{
throw new ArgumentNullException("container");
}
_container = container;
}
public object GetService(Type serviceType)
{
try
{
return _container.Resolve(serviceType);
}
catch (ResolutionFailedException)
{
return null;
}
}
public IEnumerable<object> GetServices(Type serviceType)
{
try
{
return _container.ResolveAll(serviceType);
}
catch (ResolutionFailedException)
{
return new List<object>();
}
}
public IDependencyScope BeginScope()
{
var child = _container.CreateChildContainer();
return new UnityResolver(child);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
_container.Dispose();
}
_disposed = true;
}
}
Again nothing special about this, as it was just copied from one of the many examples in the web.
Now the ControllerAction:
public async Task<IHttpActionResult> Upload()
{
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
MultipartMemoryStreamProvider provider = new MultipartMemoryStreamProvider();
await Request.Content.ReadAsMultipartAsync(provider);
IList<MyFile> uploadFiles = new List<MyFile>();
foreach (var file in provider.Contents)
{
MyFile myFile = new MyFile
{
FileName = file.Headers.ContentDisposition.FileName.Trim('\"'),
Data = await file.ReadAsByteArrayAsync()
};
uploadFiles.Add(myFile);
}
List<ConversionResult> result = new List<ConversionResult>();
foreach(MyFile file in uploadFiles)
{
result.AddRange(_fileConversionService.ImportFile(file));
}
if (result!= null && result.Count() == uploadFiles.Count)
{
return Ok(importedParts);
}
return BadRequest("One or more files could not be imported.");
}
Now the service:
public IEnumerable<ConversionResult> ImportFile(MyFile myFile)
{
lock (_lock)
{
IEnumerable<ConversionResult> conversionResult = new List<ConversionResult>();
try
{
string inputFile = SaveFileTemporarily(myFile);
string inputFileExtension = Path.GetExtension(inputFile);
string logFile = inputFile.Replace(inputFileExtension, ".txt");
// check if the file can be validated/converted by the DLL
if (ValidateFileExtension(inputFileExtension))
{
// call external DLL that writes log file and converted file
ConvertFile(inputFile, logFile, inputFileExtension);
conversionResult = ParseLogFile(logFile);
if (!conversionResult.Any())
{
// no errors found
conversionResult.Add(ConversionResult.NoError);
}
}
else
{
conversionResult.Add(ConversionResult.UnknownFileType);
}
}
finally
{
Directory.Delete(GenerateTempSavePath(myFile), true);
}
return conversionResult;
}
}
In SaveFileTemporarily()
the file is stored to some folder, actually a sub folder of the bin folder; e.g. Directory.CreateDirectory(savePath);
and File.WriteAllBytes(fullPath, data);
By debugging that I found out that I always break twice in the IDependencyResolver.Dispose()
function which is not the case for any other controller action. By commenting stuff in the service method ImportFile()
I found out that this has to do something with the file operations. So when I completely remove the System.IO
namespace from the service class I do not get that behavior and everything seems to be fine.
I'm really lost at this now as I hunt this bug for several days now. Does anyone have an idea what the problem is?
UPDATE:
When using the API from a website I also often (but not always) get this error: No 'Access-Control-Allow-Origin' header is present on the requested resource.
This leads to CORs
which is also configured in the Owin Startup (app.UseCors(CorsOptions.AllowAll);
).
This all hints to a more or less random disposal/crash of the whole Owin app. After those errors the Owin Startup method is called again which should not happen. When does the Owin Startup is being called?
When activating all Exceptions in Visual Studio (DEBUG -> Exceptions) I don't get anything...
UPDATE AND SOLUTION:
Well I don't really know why, but the problem is solved. In the ImportFile()
method of the service class is a call to SaveFileTemporarily()
. This method uses a path that I searched in the constructor of the class:
string codeBase = Assembly.GetExecutingAssembly().CodeBase;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
_tempFilePath = Path.GetDirectoryName(path) + TempDirectory;
Now here is the problem. When I use
_tempFilePath = AppDomain.CurrentDomain.BaseDirectory + TempDirectory;
everything works fine.
I guess it has to do something with the shadow-copying...