From c8087d1f6847d9be8915cfe95762e33fcb5827df Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 4 Feb 2015 17:04:40 +0100 Subject: [PATCH 1/4] Cleanup TypeHelper, add tests - IsAssignableFromGeneric may have issues? --- src/Umbraco.Core/TypeHelper.cs | 131 ++++++------- .../ExtensionMethodFinderTests.cs | 1 + src/Umbraco.Tests/Plugins/TypeHelperTests.cs | 174 ++++++++++++++++-- 3 files changed, 225 insertions(+), 81 deletions(-) diff --git a/src/Umbraco.Core/TypeHelper.cs b/src/Umbraco.Core/TypeHelper.cs index 63dd60fba9..e1a25b8e8e 100644 --- a/src/Umbraco.Core/TypeHelper.cs +++ b/src/Umbraco.Core/TypeHelper.cs @@ -330,94 +330,99 @@ namespace Umbraco.Core //TODO: Need to determine if these methods should replace/combine/merge etc with IsTypeAssignableFrom, IsAssignableFromGeneric - private static void ReduceGenericParameterCandidateTypes(ICollection allStuff, Type type) + // readings: + // http://stackoverflow.com/questions/2033912/c-sharp-variance-problem-assigning-listderived-as-listbase + // http://stackoverflow.com/questions/2208043/generic-variance-in-c-sharp-4-0 + // http://stackoverflow.com/questions/8401738/c-sharp-casting-generics-covariance-and-contravariance + // http://stackoverflow.com/questions/1827425/how-to-check-programatically-if-a-type-is-a-struct-or-a-class + // http://stackoverflow.com/questions/74616/how-to-detect-if-type-is-another-generic-type/1075059#1075059 + + private static bool MatchGeneric(Type implementation, Type contract, IDictionary bindings) { - var at1 = new List(); - var t = type; - while (t != null) + // trying to match eg List with List + // or List>> with List>> + // classes are NOT invariant so List does not match List + + if (implementation.IsGenericType == false) return false; + + // must have the same generic type definition + var implDef = implementation.GetGenericTypeDefinition(); + var contDef = contract.GetGenericTypeDefinition(); + if (implDef != contDef) return false; + + // must have the same number of generic arguments + var implArgs = implementation.GetGenericArguments(); + var contArgs = contract.GetGenericArguments(); + if (implArgs.Length != contArgs.Length) return false; + + // generic arguments must match + // in insta we should have actual types (eg int, string...) + // in typea we can have generic parameters (eg ) + for (var i = 0; i < implArgs.Length; i++) { - at1.Add(t); - t = t.BaseType; - } - var r = allStuff.Where(x => x.IsClass && at1.Contains(x) == false).ToArray(); - foreach (var x in r) allStuff.Remove(x); - var ai1 = type.GetInterfaces(); - if (type.IsInterface) ai1 = ai1.Union(new[] { type }).ToArray(); - r = allStuff.Where(x => x.IsInterface && ai1.Contains(x) == false).ToArray(); - foreach (var x in r) allStuff.Remove(x); - } - - private static bool MatchGeneric(Type inst, Type type, IDictionary> bindings) - { - if (inst.IsGenericType == false) return false; - - var instd = inst.GetGenericTypeDefinition(); - var typed = type.GetGenericTypeDefinition(); - - if (instd != typed) return false; - - var insta = inst.GetGenericArguments(); - var typea = type.GetGenericArguments(); - - if (insta.Length != typea.Length) return false; - - // but... there is no ZipWhile, and we have arrays anyway - //var x = insta.Zip(typea, (instax, typeax) => { ... }); - - for (var i = 0; i < insta.Length; i++) - if (MatchType(insta[i], typea[i], bindings) == false) + const bool variance = false; // classes are NOT invariant + if (MatchType(implArgs[i], contArgs[i], bindings, variance) == false) return false; + } return true; } - private static IEnumerable GetGenericParameterCandidateTypes(Type type) + public static bool MatchType(Type implementation, Type contract) { - yield return type; - var t = type.BaseType; - while (t != null) - { - yield return t; - t = t.BaseType; - } - foreach (var i in type.GetInterfaces()) - yield return i; + return MatchType(implementation, contract, new Dictionary()); } - public static bool MatchType(Type inst, Type type) + internal static bool MatchType(Type implementation, Type contract, IDictionary bindings, bool variance = true) { - return MatchType(inst, type, new Dictionary>()); - } - - internal static bool MatchType(Type inst, Type type, IDictionary> bindings) - { - if (type.IsGenericType) + if (contract.IsGenericType) { - if (MatchGeneric(inst, type, bindings)) return true; - var t = inst.BaseType; + // eg type is List or List + // if we have variance then List can match IList + // if we don't have variance it can't - must have exact type + + // try to match implementation against contract + if (MatchGeneric(implementation, contract, bindings)) return true; + + // if no variance, fail + if (variance == false) return false; + + // try to match an ancestor of implementation against contract + var t = implementation.BaseType; while (t != null) { - if (MatchGeneric(t, type, bindings)) return true; + if (MatchGeneric(t, contract, bindings)) return true; t = t.BaseType; } - return inst.GetInterfaces().Any(i => MatchGeneric(i, type, bindings)); + + // try to match an interface of implementation against contract + return implementation.GetInterfaces().Any(i => MatchGeneric(i, contract, bindings)); } - if (type.IsGenericParameter) + if (contract.IsGenericParameter) { - if (bindings.ContainsKey(type.Name)) + // eg + + if (bindings.ContainsKey(contract.Name)) { - ReduceGenericParameterCandidateTypes(bindings[type.Name], inst); - return bindings[type.Name].Count > 0; + // already bound: ensure it's compatible + return bindings[contract.Name] == implementation; } - bindings[type.Name] = new List(GetGenericParameterCandidateTypes(inst)); + // not already bound: bind + bindings[contract.Name] = implementation; return true; } - if (inst == type) return true; - if (type.IsClass && inst.IsClass && inst.IsSubclassOf(type)) return true; - if (type.IsInterface && inst.GetInterfaces().Contains(type)) return true; + // not a generic type, not a generic parameter + // so normal class or interface + // fixme structs? enums? array types? + // about primitive types, value types, etc: + // http://stackoverflow.com/questions/1827425/how-to-check-programatically-if-a-type-is-a-struct-or-a-class + + if (implementation == contract) return true; + if (contract.IsClass && implementation.IsClass && implementation.IsSubclassOf(contract)) return true; + if (contract.IsInterface && implementation.GetInterfaces().Contains(contract)) return true; return false; } diff --git a/src/Umbraco.Tests/DynamicsAndReflection/ExtensionMethodFinderTests.cs b/src/Umbraco.Tests/DynamicsAndReflection/ExtensionMethodFinderTests.cs index aab71e364a..33965c40c5 100644 --- a/src/Umbraco.Tests/DynamicsAndReflection/ExtensionMethodFinderTests.cs +++ b/src/Umbraco.Tests/DynamicsAndReflection/ExtensionMethodFinderTests.cs @@ -29,6 +29,7 @@ namespace Umbraco.Tests.DynamicsAndReflection public void TestMethod6(int value) { } public void TestMethod6(string value) { } public void TestMethod7(IList value) { } + public void TestMethod8(IDictionary value) { } public interface ITestDict : IDictionary { } diff --git a/src/Umbraco.Tests/Plugins/TypeHelperTests.cs b/src/Umbraco.Tests/Plugins/TypeHelperTests.cs index 9741e9cb2e..25c26c1432 100644 --- a/src/Umbraco.Tests/Plugins/TypeHelperTests.cs +++ b/src/Umbraco.Tests/Plugins/TypeHelperTests.cs @@ -75,8 +75,7 @@ namespace Umbraco.Tests.Plugins .Single() .ParameterType; - Assert.IsTrue(TypeHelper.IsAssignableFromGeneric(genericEnumerableNonGenericDefinition, typeof(List))); - + Assert.IsTrue(TypeHelper.IsAssignableFromGeneric(genericEnumerableNonGenericDefinition, typeof(List))); } class Base { } @@ -144,47 +143,186 @@ namespace Umbraco.Tests.Plugins [Test] public void MatchTypesTest() { - var bindings = new Dictionary>(); + var bindings = new Dictionary(); Assert.IsTrue(TypeHelper.MatchType(typeof(int), typeof(int), bindings)); Assert.AreEqual(0, bindings.Count); - bindings = new Dictionary>(); + bindings = new Dictionary(); Assert.IsFalse(TypeHelper.MatchType(typeof(int), typeof(string), bindings)); Assert.AreEqual(0, bindings.Count); - bindings = new Dictionary>(); + bindings = new Dictionary(); Assert.IsTrue(TypeHelper.MatchType(typeof(List), typeof(System.Collections.IEnumerable), bindings)); Assert.AreEqual(0, bindings.Count); - var m = typeof(ExtensionMethodFinderTests).GetMethod("TestMethod7"); - var t1 = m.GetParameters()[0].ParameterType; // List - var t2 = m.GetParameters()[0].ParameterType.GetGenericArguments()[0]; // + var t1 = typeof(IList<>); // IList<> + var a1 = t1.GetGenericArguments()[0]; // + t1 = t1.MakeGenericType(a1); // IList + var t2 = a1; - bindings = new Dictionary>(); + bindings = new Dictionary(); Assert.IsTrue(TypeHelper.MatchType(typeof(int), t2, bindings)); Assert.AreEqual(1, bindings.Count); - Assert.AreEqual(typeof(int), bindings["T"].FirstOrDefault()); + Assert.AreEqual(typeof(int), bindings["T"]); - bindings = new Dictionary>(); + bindings = new Dictionary(); Assert.IsTrue(TypeHelper.MatchType(typeof(IList), t1, bindings)); Assert.AreEqual(1, bindings.Count); - Assert.AreEqual(typeof(int), bindings["T"].FirstOrDefault()); + Assert.AreEqual(typeof(int), bindings["T"]); - bindings = new Dictionary>(); + bindings = new Dictionary(); Assert.IsTrue(TypeHelper.MatchType(typeof(List), typeof(IList), bindings)); Assert.AreEqual(0, bindings.Count); - bindings = new Dictionary>(); + bindings = new Dictionary(); Assert.IsTrue(TypeHelper.MatchType(typeof(List), t1, bindings)); Assert.AreEqual(1, bindings.Count); - Assert.AreEqual(typeof(int), bindings["T"].FirstOrDefault()); + Assert.AreEqual(typeof(int), bindings["T"]); - bindings = new Dictionary>(); + bindings = new Dictionary(); Assert.IsTrue(TypeHelper.MatchType(typeof(Dictionary), typeof(IDictionary<,>), bindings)); Assert.AreEqual(2, bindings.Count); - Assert.AreEqual(typeof(int), bindings["TKey"].FirstOrDefault()); - Assert.AreEqual(typeof(string), bindings["TValue"].FirstOrDefault()); + Assert.AreEqual(typeof(int), bindings["TKey"]); + Assert.AreEqual(typeof(string), bindings["TValue"]); + + t1 = typeof(IDictionary<,>); // IDictionary<,> + a1 = t1.GetGenericArguments()[0]; // + t1 = t1.MakeGenericType(a1, a1); // IDictionary + + bindings = new Dictionary(); + Assert.IsFalse(TypeHelper.MatchType(typeof(Dictionary), t1, bindings)); + + bindings = new Dictionary(); + Assert.IsTrue(TypeHelper.MatchType(typeof(Dictionary), t1, bindings)); + Assert.AreEqual(1, bindings.Count); + Assert.AreEqual(typeof(int), bindings["TKey"]); } + [Test] + public void MatchType_Vs_IsAssignableFromGeneric() + { + // both are OK + Assert_MatchType_Vs_IsAssignableFromGeneric(true, typeof(List), typeof(IEnumerable<>)); + + var t1 = typeof (IDictionary<,>); // IDictionary<,> + var a1 = t1.GetGenericArguments()[0]; + t1 = t1.MakeGenericType(a1, a1); // IDictionary + + // both are OK + Assert_MatchType_Vs_IsAssignableFromGeneric(true, typeof(Dictionary), t1); + + // TODO FIXME - IsAssignableFromGeneric returns true! does not manage generic parameters binding + Assert_MatchType_Vs_IsAssignableFromGeneric(false, typeof(Dictionary), t1); + + // these are all of there from Is_Assignable_To_Generic_Type + Assert_MatchType_Vs_IsAssignableFromGeneric(true, typeof(Derived), typeof(Base<>)); + Assert_MatchType_Vs_IsAssignableFromGeneric(true, typeof(List), typeof(IEnumerable<>)); + Assert_MatchType_Vs_IsAssignableFromGeneric(true, typeof(Derived), typeof(Derived<>)); + Assert_MatchType_Vs_IsAssignableFromGeneric(true, typeof(Derived2), typeof(Base<>)); + Assert_MatchType_Vs_IsAssignableFromGeneric(true, typeof(DerivedI), typeof(IBase<>)); + Assert_MatchType_Vs_IsAssignableFromGeneric(true, typeof(Derived2), typeof(IBase<>)); + Assert_MatchType_Vs_IsAssignableFromGeneric(true, typeof(int?), typeof(Nullable<>)); + + // TODO FIXME - those that are marked below should actually be (true,...) but were false in Is_Assignable_To_Generic_Type + + // TODO FIXME - why would IsAssignableFromGeneric returns false? Derived inherits from Object + Assert_MatchType_Vs_IsAssignableFromGeneric(false, typeof(Derived), typeof(Object)); + Assert_MatchType_Vs_IsAssignableFromGeneric(false, typeof(Derived), typeof(List<>)); + Assert_MatchType_Vs_IsAssignableFromGeneric(false, typeof(Derived), typeof(IEnumerable<>)); + // TODO FIXME - why would IsAssignableFromGeneric returns false? Derived inherits from Base + Assert_MatchType_Vs_IsAssignableFromGeneric(false, typeof(Derived), typeof(Base)); + // TODO FIXME - why would IsAssignableFromGeneric returns false? List implements IEnumerable + Assert_MatchType_Vs_IsAssignableFromGeneric(false, typeof(List), typeof(IEnumerable)); + Assert_MatchType_Vs_IsAssignableFromGeneric(false, typeof(int), typeof(Nullable<>)); + + //This get's the "Type" from the Count extension method on IEnumerable, however the type IEnumerable isn't + // IEnumerable<> and it is not a generic definition, this attempts to explain that: + // http://blogs.msdn.com/b/haibo_luo/archive/2006/02/17/534480.aspx + + var genericEnumerableNonGenericDefinition = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public) + .Single(x => x.Name == "Count" && x.GetParameters().Count() == 1) + .GetParameters() + .Single() + .ParameterType; + + Assert_MatchType_Vs_IsAssignableFromGeneric(true, typeof(List), genericEnumerableNonGenericDefinition); + + } + + private void Assert_MatchType_Vs_IsAssignableFromGeneric(bool expected, Type implementation, Type contract) + { + if (expected) + { + Assert.IsTrue(TypeHelper.MatchType(implementation, contract)); + Assert.IsTrue(TypeHelper.IsAssignableFromGeneric(contract, implementation)); + } + else + { + Assert.IsFalse(TypeHelper.MatchType(implementation, contract)); + Assert.IsFalse(TypeHelper.IsAssignableFromGeneric(contract, implementation)); + } + } + + [Test] + public void CreateOpenGenericTypes() + { + // readings + // http://stackoverflow.com/questions/13466078/create-open-constructed-type-from-string + // http://stackoverflow.com/questions/6704722/c-sharp-language-how-to-get-type-of-bound-but-open-generic-class + + // note that FullName returns "The fully qualified name of the type, including its namespace but not its + // assembly; or null if the current instance represents a generic type parameter, an array type, pointer + // type, or byref type based on a type parameter, or a generic type that is not a generic type definition + // but contains unresolved type parameters." + + var t = Type.GetType("System.Collections.Generic.IList`1"); + Assert.IsNotNull(t); + Assert.IsTrue(t.IsGenericTypeDefinition); + Assert.AreEqual("IList`1", t.Name); + Assert.AreEqual("System.Collections.Generic.IList`1", t.FullName); + Assert.AreEqual("System.Collections.Generic.IList`1[T]", t.ToString()); + + t = typeof (IList<>); + Assert.IsTrue(t.IsGenericTypeDefinition); + Assert.AreEqual("IList`1", t.Name); + Assert.AreEqual("System.Collections.Generic.IList`1", t.FullName); + Assert.AreEqual("System.Collections.Generic.IList`1[T]", t.ToString()); + + t = typeof(IDictionary<,>); + Assert.IsTrue(t.IsGenericTypeDefinition); + Assert.AreEqual("IDictionary`2", t.Name); + Assert.AreEqual("System.Collections.Generic.IDictionary`2", t.FullName); + Assert.AreEqual("System.Collections.Generic.IDictionary`2[TKey,TValue]", t.ToString()); + + t = typeof(IDictionary<,>); + t = t.MakeGenericType(t.GetGenericArguments()[0], t.GetGenericArguments()[1]); + Assert.AreEqual("IDictionary`2", t.Name); + Assert.AreEqual("System.Collections.Generic.IDictionary`2", t.FullName); + Assert.AreEqual("System.Collections.Generic.IDictionary`2[TKey,TValue]", t.ToString()); + + t = typeof(IDictionary<,>); + t = t.MakeGenericType(t.GetGenericArguments()[0], t.GetGenericArguments()[0]); + Assert.IsFalse(t.IsGenericTypeDefinition); // not anymore + Assert.AreEqual("IDictionary`2", t.Name); + Assert.IsNull(t.FullName); // see note above + Assert.AreEqual("System.Collections.Generic.IDictionary`2[TKey,TKey]", t.ToString()); + + t = typeof(IList<>); + Assert.IsTrue(t.IsGenericTypeDefinition); + t = t.MakeGenericType(t.GetGenericArguments()[0]); + Assert.AreEqual("IList`1", t.Name); + Assert.AreEqual("System.Collections.Generic.IList`1", t.FullName); + Assert.AreEqual("System.Collections.Generic.IList`1[T]", t.ToString()); + + t = typeof(IList); + Assert.AreEqual("System.Collections.Generic.IList`1[System.Int32]", t.ToString()); + + t = typeof (IDictionary<,>); + t = t.MakeGenericType(typeof(int), t.GetGenericArguments()[1]); + Assert.IsFalse(t.IsGenericTypeDefinition); // not anymore + Assert.AreEqual("IDictionary`2", t.Name); + Assert.IsNull(t.FullName); // see note above + Assert.AreEqual("System.Collections.Generic.IDictionary`2[System.Int32,TValue]", t.ToString()); + } } } \ No newline at end of file From de53dbffd1bcc79e3dd5063aa1740b28011123b1 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 4 Feb 2015 19:07:51 +0100 Subject: [PATCH 2/4] U4-6129 - fix order of EntityService.GetByIds items --- src/Umbraco.Web/Editors/EntityController.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 7cebeb6252..b12d19d032 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -6,6 +6,7 @@ using System.Text; using System.Web.Http; using System.Web.Http.ModelBinding; using AutoMapper; +using ClientDependency.Core; using Examine.LuceneEngine; using Examine.LuceneEngine.Providers; using Newtonsoft.Json; @@ -637,15 +638,20 @@ namespace Umbraco.Web.Editors private IEnumerable GetResultForKeys(IEnumerable keys, UmbracoEntityTypes entityType) { - if (keys.Any() == false) return Enumerable.Empty(); + var keysArray = keys.ToArray(); + if (keysArray.Any() == false) return Enumerable.Empty(); var objectType = ConvertToObjectType(entityType); if (objectType.HasValue) { - var result = Services.EntityService.GetAll(objectType.Value, keys.ToArray()) + var entities = Services.EntityService.GetAll(objectType.Value, keysArray) .WhereNotNull() .Select(Mapper.Map); + // entities are in "some" order, put them back in order + var xref = entities.ToDictionary(x => x.Id); + var result = keysArray.Select(x => xref.ContainsKey(x) ? xref[x] : null).Where(x => x != null); + return result; } //now we need to convert the unknown ones @@ -664,15 +670,20 @@ namespace Umbraco.Web.Editors private IEnumerable GetResultForIds(IEnumerable ids, UmbracoEntityTypes entityType) { - if (ids.Any() == false) return Enumerable.Empty(); + var idsArray = ids.ToArray(); + if (idsArray.Any() == false) return Enumerable.Empty(); var objectType = ConvertToObjectType(entityType); if (objectType.HasValue) { - var result = Services.EntityService.GetAll(objectType.Value, ids.ToArray()) + var entities = Services.EntityService.GetAll(objectType.Value, idsArray) .WhereNotNull() .Select(Mapper.Map); + // entities are in "some" order, put them back in order + var xref = entities.ToDictionary(x => x.Id); + var result = idsArray.Select(x => xref.ContainsKey(x) ? xref[x] : null).Where(x => x != null); + return result; } //now we need to convert the unknown ones From 4790dc59a986cfcdd6e796c305a12b1ff0459528 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 5 Feb 2015 09:01:42 +0100 Subject: [PATCH 3/4] Update CDF dependency --- build/NuSpecs/UmbracoCms.Core.nuspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index feb530ce99..2d738475db 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -28,7 +28,7 @@ - + From 89606d3a0572b76895f0ee655087cda68d84daee Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 6 Feb 2015 11:19:52 +1100 Subject: [PATCH 4/4] Goes brute force to try to resolve: U4-4049 Some images fail when uploading multiple media files --- src/Umbraco.Core/IO/FileSystemExtensions.cs | 32 +++++++++++++++++++++ src/Umbraco.Web/Editors/MediaController.cs | 7 ++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/IO/FileSystemExtensions.cs b/src/Umbraco.Core/IO/FileSystemExtensions.cs index f2152afaab..64dcfc25a0 100644 --- a/src/Umbraco.Core/IO/FileSystemExtensions.cs +++ b/src/Umbraco.Core/IO/FileSystemExtensions.cs @@ -1,10 +1,42 @@ using System; using System.IO; +using System.Threading; namespace Umbraco.Core.IO { public static class FileSystemExtensions { + + /// + /// Attempts to open the file at filePath up to maxRetries times, + /// with a thread sleep time of sleepPerRetryInMilliseconds between retries. + /// + public static FileStream OpenReadWithRetry(this FileInfo file, int maxRetries = 5, int sleepPerRetryInMilliseconds = 50) + { + var retries = maxRetries; + + while (retries > 0) + { + try + { + return File.OpenRead(file.FullName); + } + catch(IOException) + { + retries--; + + if (retries == 0) + { + throw; + } + + Thread.Sleep(sleepPerRetryInMilliseconds); + } + } + + throw new ArgumentException("Retries must be greater than zero"); + } + public static long GetSize(this IFileSystem fs, string path) { using (var file = fs.OpenFile(path)) diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index d807d84a26..861750e752 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -32,6 +32,7 @@ using umbraco; using umbraco.BusinessLogic.Actions; using Constants = Umbraco.Core.Constants; using Umbraco.Core.Configuration; +using Umbraco.Core.Persistence.FaultHandling; namespace Umbraco.Web.Editors { @@ -404,7 +405,11 @@ namespace Umbraco.Web.Editors var mediaService = ApplicationContext.Services.MediaService; var f = mediaService.CreateMedia(fileName, parentId, mediaType); - using (var fs = System.IO.File.OpenRead(file.LocalFileName)) + + var fileInfo = new FileInfo(file.LocalFileName); + var fs = fileInfo.OpenReadWithRetry(); + if (fs == null) throw new InvalidOperationException("Could not acquire file stream"); + using (fs) { f.SetValue(Constants.Conventions.Media.File, fileName, fs); }