{"action":"created","issue":{"url":"https://api.github.com/repos/aspnet/Mvc/issues/5603","repository_url":"https://api.github.com/repos/aspnet/Mvc","labels_url":"https://api.github.com/repos/aspnet/Mvc/issues/5603/labels{/name}","comments_url":"https://api.github.com/repos/aspnet/Mvc/issues/5603/comments","events_url":"https://api.github.com/repos/aspnet/Mvc/issues/5603/events","html_url":"https://github.com/aspnet/Mvc/issues/5603","id":194093184,"number":5603,"title":"IModelBinder: Timeouts are not support exception","user":{"login":"markolbert","id":9095610,"avatar_url":"https://avatars.githubusercontent.com/u/9095610?v=3","gravatar_id":"","url":"https://api.github.com/users/markolbert","html_url":"https://github.com/markolbert","followers_url":"https://api.github.com/users/markolbert/followers","following_url":"https://api.github.com/users/markolbert/following{/other_user}","gists_url":"https://api.github.com/users/markolbert/gists{/gist_id}","starred_url":"https://api.github.com/users/markolbert/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/markolbert/subscriptions","organizations_url":"https://api.github.com/users/markolbert/orgs","repos_url":"https://api.github.com/users/markolbert/repos","events_url":"https://api.github.com/users/markolbert/events{/privacy}","received_events_url":"https://api.github.com/users/markolbert/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":4,"created_at":"2016-12-07T16:14:27Z","updated_at":"2016-12-07T20:35:11Z","closed_at":null,"body":"I am looking at ways to parse multipart post payloads which incorporate file uploads. I have a working solution which processes the raw request stream, but I thought I'd convert it to a custom model binder so it would blend in with the usual mvc semantics (e.g., public IActionResult Action([ModelBinder(...) SomeModelType model...).\r\n\r\nFYI, this is intended to handle situations where some client-side angular code executes a POST against an MVC Core action.\r\n\r\nThe custom model binder binds information from the request stream to the target model, but as soon as BindModelAsync() returns I get a \"timeouts are not supported on this stream\" exception. The exception occurs in what appears to be an unrelated piece of middleware (which was working fine for quite a while). But I've seen exceptions pop up in unexpected places before when using multithreaded logic.\r\n\r\nHere's the binder code:\r\n\r\n```\r\n        public async Task BindModelAsync( ModelBindingContext bindingContext )\r\n        {\r\n            if( bindingContext == null )\r\n                throw new ArgumentNullException( nameof( bindingContext ) );\r\n\r\n            if( bindingContext.HttpContext?.Request == null ) return;\r\n\r\n            // make sure the model type we're binding to has a property of ICollection<IUploadedFile>\r\n            var fileProps = bindingContext.ModelMetadata\r\n                .Properties\r\n                .Where( p => p.IsEnumerableType && typeof(IUploadedFile).IsAssignableFrom(p.ElementType) );\r\n\r\n            if( fileProps.Count() != 1 ) return;\r\n\r\n            var fileProp = fileProps.First();\r\n\r\n            HttpRequest request = bindingContext.HttpContext.Request;\r\n\r\n            if( bindingContext.Model == null )\r\n            {\r\n                var modelCreator =\r\n                    Expression.Lambda<Func<object>>( Expression.New( bindingContext.ModelType ) ).Compile();\r\n\r\n                bindingContext.Model = modelCreator();\r\n            }\r\n\r\n            fileProp.PropertySetter( bindingContext.Model, new List<T>() );\r\n\r\n            var boundary = MultipartRequestHelper.GetBoundary(\r\n                           MediaTypeHeaderValue.Parse( request.ContentType ), _maxSectionSize );\r\n\r\n            var reader = new MultipartReader( boundary, request.Body );\r\n\r\n            var section = await reader.ReadNextSectionAsync();\r\n            var values = new Dictionary<string, StringValues>( StringComparer.CurrentCultureIgnoreCase );\r\n \r\n            while( section != null )\r\n            {\r\n                ContentDispositionHeaderValue contentDisposition;\r\n                ContentDispositionHeaderValue.TryParse( section.ContentDisposition, out contentDisposition );\r\n\r\n                if( MultipartRequestHelper.HasFileContentDisposition( contentDisposition ) )\r\n                {\r\n                    var name = HeaderUtilities.RemoveQuotes( contentDisposition.Name ) ?? string.Empty;\r\n\r\n                    T newFI = new T()\r\n                    {\r\n                        FileName = HeaderUtilities.RemoveQuotes( contentDisposition.FileName ) ?? string.Empty\r\n                    };\r\n\r\n                    await section.Body.CopyToAsync( newFI.Stream );\r\n\r\n                    ( (List<T>) fileProp.PropertyGetter( bindingContext.Model ) ).Add( newFI );\r\n                }\r\n                else\r\n                {\r\n                    if( MultipartRequestHelper.HasFormDataContentDisposition( contentDisposition ) )\r\n                    {\r\n                        // Content-Disposition: form-data; name=\"key\"\r\n\r\n                        // Do not limit the key name length here because the mulipart headers length\r\n                        // limit is already in effect.\r\n                        var key = HeaderUtilities.RemoveQuotes( contentDisposition.Name );\r\n\r\n                        MediaTypeHeaderValue mediaType;\r\n                        MediaTypeHeaderValue.TryParse( section.ContentType, out mediaType );\r\n\r\n                        var encoding = FilterEncoding( mediaType?.Encoding );\r\n\r\n                        using( var streamReader = new StreamReader(\r\n                            section.Body,\r\n                            encoding,\r\n                            detectEncodingFromByteOrderMarks: true,\r\n                            bufferSize: 1024,\r\n                            leaveOpen: true ) )\r\n                        {\r\n                            // The value length limit is enforced by MultipartBodyLengthLimit\r\n                            var value = await streamReader.ReadToEndAsync();\r\n                            values.Add( key, value );\r\n                        }\r\n                    }\r\n                }\r\n\r\n                // Drains any remaining section body that has not been consumed and\r\n                // reads the headers for the next section.\r\n                section = await reader.ReadNextSectionAsync();\r\n            }\r\n\r\n            foreach( var property in bindingContext.ModelMetadata.Properties )\r\n            {\r\n                if( values.ContainsKey( property.PropertyName ) )\r\n                {\r\n                    if( property.IsEnumerableType )\r\n                    {\r\n                        var converter = TypeDescriptor.GetConverter( property.ElementType );\r\n\r\n                        List<object> converted = values[ property.PropertyName ]\r\n                            .Select( v => converter.ConvertFrom( v ) )\r\n                            .ToList();\r\n\r\n                        property.PropertySetter( bindingContext.Model, converted );\r\n                    }\r\n                    else\r\n                    {\r\n                        var converter = TypeDescriptor.GetConverter( property.ModelType );\r\n\r\n                        property.PropertySetter(\r\n                            bindingContext.Model,\r\n                            converter.ConvertFrom( values[ property.PropertyName ][ 0 ] ) );\r\n                    }\r\n                }\r\n            }\r\n\r\n            bindingContext.Result = ModelBindingResult.Success( bindingContext.Model );\r\n        }\r\n\r\n```\r\nI realize this approach doesn't \"fit\" well within the model binding framework (and has a number of other limitations, such as only supporting simple properties). OTOH, based on what I've read, mixing file upload information in a POST triggered by client-side code (angular in this instance) is fraught with a lot of pitfalls, so a limited working solution suits me just fine :).\r\n\r\nThoughts? And thanx, BTW, to the team members who posted the original version of this code over on the Entropy site."},"comment":{"url":"https://api.github.com/repos/aspnet/Mvc/issues/comments/265565515","html_url":"https://github.com/aspnet/Mvc/issues/5603#issuecomment-265565515","issue_url":"https://api.github.com/repos/aspnet/Mvc/issues/5603","id":265565515,"user":{"login":"markolbert","id":9095610,"avatar_url":"https://avatars.githubusercontent.com/u/9095610?v=3","gravatar_id":"","url":"https://api.github.com/users/markolbert","html_url":"https://github.com/markolbert","followers_url":"https://api.github.com/users/markolbert/followers","following_url":"https://api.github.com/users/markolbert/following{/other_user}","gists_url":"https://api.github.com/users/markolbert/gists{/gist_id}","starred_url":"https://api.github.com/users/markolbert/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/markolbert/subscriptions","organizations_url":"https://api.github.com/users/markolbert/orgs","repos_url":"https://api.github.com/users/markolbert/repos","events_url":"https://api.github.com/users/markolbert/events{/privacy}","received_events_url":"https://api.github.com/users/markolbert/received_events","type":"User","site_admin":false},"created_at":"2016-12-07T20:35:11Z","updated_at":"2016-12-07T20:35:11Z","body":"Okay, thanx. I'll see if it goes away upon upgrade."}}
{"actor":{"display_login":"markolbert"}}