Re-implement Contrib.FileField for Orchard 1.8

Topics: Customizing Orchard, Writing modules
Oct 4, 2014 at 11:39 AM
After trying to add upload file functionality to Orchard custom form and trying all modules for that, I decide to re-implement Contrib.FileField by replace IMediaServices with IStorageProvider, but I need any API documentation to do so.

Any help?
Oct 8, 2014 at 8:09 AM
Edited Oct 8, 2014 at 8:28 AM
I did the same, and just looked at the IStorageProvider's implementation: Orchard.Framework->FileSystems->Media->FileSystemStorageProvider.

I ended up with the following filefield driver implementation (Note: I did this when I started with Orchard, never took the time to perfect/optimize it):
public class FileFieldDriver : ContentFieldDriver<Fields.FileField> {
        private const string TokenContentType = "{content-type}";
        private const string TokenFieldName = "{field-name}";
        private const string TokenContentItemId = "{content-item-id}";

        private readonly IStorageProvider _storageProvider;
        public Localizer T { get; set; }

        public FileFieldDriver(IStorageProvider storageProvider)
        {
            _storageProvider = storageProvider;
            T = NullLocalizer.Instance;
        }

        private static string GetPrefix(ContentField field, ContentPart part)
        {
            return part.PartDefinition.Name + "." + field.Name;
        }

        private string GetDifferentiator(ContentField field, ContentPart part)
        {
            return field.Name;
        }

        protected override DriverResult Display(ContentPart part, Fields.FileField field, string displayType, dynamic shapeHelper)
        {
            var settings = field.PartFieldDefinition.Settings.GetModel<FileFieldSettings>();
            return ContentShape("Fields_Devq_File", GetDifferentiator(field, part), () => shapeHelper.Fields_Contrib_File(ContentPart: part, ContentField: field, Settings: settings));
        }

        protected override DriverResult Editor(ContentPart part, Fields.FileField field, dynamic shapeHelper)
        {
            var settings = field.PartFieldDefinition.Settings.GetModel<FileFieldSettings>();

            AssignDefaultMediaFolder(settings);

            var viewModel = new FileFieldViewModel
            {
                Settings = settings,
                Field = field
            };

            return ContentShape("Fields_Devq_File_Edit", GetDifferentiator(field, part),
                                () => shapeHelper.EditorTemplate(TemplateName: "Fields/Devq.File", Model: viewModel, Prefix: GetPrefix(field, part)));
        }

        protected override DriverResult Editor(ContentPart part, Fields.FileField field, IUpdateModel updater, dynamic shapeHelper)
        {
            var settings = field.PartFieldDefinition.Settings.GetModel<FileFieldSettings>();
            var viewModel = new FileFieldViewModel
            {
                Settings = settings,
                Field = field
            };

            if (updater.TryUpdateModel(viewModel, GetPrefix(field, part), null, null))
            {
                var postedFile = ((Controller)updater).Request.Files["FileField-" + field.Name];

                AssignDefaultMediaFolder(settings);

                var mediaFolder = FormatWithTokens(settings.MediaFolder, part.ContentItem.ContentType, field.Name, part.ContentItem.Id);

                if (postedFile != null && postedFile.ContentLength != 0) {
                    var extension = Path.GetExtension(postedFile.FileName);
                    var allowedExtensions = settings.Accept.Split(',');
                    if (allowedExtensions.Contains(extension))
                    {
                        var postedFileLength = postedFile.ContentLength;
                        var postedFileStream = postedFile.InputStream;
                        var postedFileData = new byte[postedFileLength];
                        var postedFileName = Path.GetFileName(postedFile.FileName);
                        postedFileStream.Read(postedFileData, 0, postedFileLength);

                        string uniqueFileName = postedFileName;

                        try
                        {
                            // try to create the folder before uploading a file into it
                            _storageProvider.CreateFolder(mediaFolder);
                        }
                        catch
                        {
                            // the folder can't be created because it already exists, continue
                        }

                        var existingFiles = _storageProvider.ListFiles(mediaFolder);
                        bool found = true;
                        var index = 0;
                        while (found)
                        {
                            index++;
                            uniqueFileName = String.Format("{0}-{1}{2}", Path.GetFileNameWithoutExtension(postedFileName), index, Path.GetExtension(postedFileName));
                            string name = uniqueFileName;
                            found = existingFiles.Any(f => 0 == String.Compare(name, f.GetName(), StringComparison.OrdinalIgnoreCase));
                        }
                        var storedFile = _storageProvider.CreateFile(mediaFolder + uniqueFileName + extension);
                        field.Path = storedFile.GetPath();
                    }
                    else
                    {
                        updater.AddModelError("File", T("The file type is not allowed for: {0}.", postedFile.FileName));
                    }
                }
                else
                {
                    if (settings.Required && string.IsNullOrWhiteSpace(field.Path))
                    {
                        updater.AddModelError("File", T("You must provide a file for {0}.", field.Name.CamelFriendly()));
                    }
                }

                if (string.IsNullOrWhiteSpace(field.Text))
                {
                    field.Text = Path.GetFileName(field.Path);
                }
            }

            return Editor(part, field, shapeHelper);
        }

        private static string FormatWithTokens(string value, string contentType, string fieldName, int contentItemId)
        {
            if (String.IsNullOrWhiteSpace(value))
            {
                return String.Empty;
            }

            return value
                .Replace(TokenContentType, contentType)
                .Replace(TokenFieldName, fieldName)
                .Replace(TokenContentItemId, Convert.ToString(contentItemId));
        }

        private static void AssignDefaultMediaFolder(FileFieldSettings settings)
        {
            if (String.IsNullOrWhiteSpace(settings.MediaFolder))
            {
                settings.MediaFolder = TokenContentType + "/" + TokenFieldName;
            }
        }
    }
Oct 8, 2014 at 9:51 AM
I've tried to implement IStorageProvider by myself (link) but still I'm saving file with 0 KB.
Oct 8, 2014 at 10:06 AM
Yeah I just looked again and it seems like this code:
_storageProvider.CreateFile(filePath);
Only creates an empty file, you still have to write the bytes to the file I think.
Oct 8, 2014 at 12:28 PM
Is your implementation working with Custom Forms?

I used
_storageProvider.CreateFile(filePath);
and
_storageProvider.TrySaveStream(filePath, postedFileStream);
and still uploading the file with 0 KB size
Oct 13, 2014 at 7:54 AM
I've re-implement it and publish it on GitHub
Marked as answer by EmadMokhtar on 10/12/2014 at 11:56 PM
Oct 14, 2014 at 12:10 AM
I was the original creator of the FileField.

I don't see the need for this anymore. Orchard now has a Media Library allowing file upload via the "Media Library Picker Field". This is a far better option going forward.
Oct 14, 2014 at 4:57 AM
Sure there is a need for it, I want online visitor to upload for example his//her resume inside careers.job vacancies form.

I found Media Library Picker Field won't give me a control over the file extension and size, or even the media folder.
Oct 14, 2014 at 8:07 AM
@EmadMokhtar do you have an account on the orchard gallery? I'll assign the module to you to take over if you want?
Oct 14, 2014 at 11:47 AM
jrmurdoch wrote:
@EmadMokhtar do you have an account on the orchard gallery? I'll assign the module to you to take over if you want?
My username is Emad.m.habib, and thank you very much for your re-assignation.
Oct 14, 2014 at 5:33 PM
The need for this module is to enable frontend users to upload files from their computer, and not be able to browse the media in the medialibrary.

Great work, I'm going to use this stuff. I also have a need to convert it to a multi-image field, also for the frontend.
Coordinator
Oct 14, 2014 at 5:48 PM
Please consider making these kind of field part of the new dynamic forms module from Sipke (in the dynamicforms branch). The module targets specifically the front end, and will let users create forms dynamically. The content can then be bound to internal content items if necessary.
Oct 16, 2014 at 9:45 AM
Great work gents. This is essential field for a number of my clients.

PK
Oct 17, 2014 at 1:12 AM
Edited Oct 17, 2014 at 1:35 AM
Emad,

Would I be stretching the friendship if I asked if its possible to register the upload with Media Library Picker?

Or maybe there could be some sort of clean-up module that just registers all files not present in Media Library, that are present in the OS Media folder?

Also, Workflows Email, could there possibly be an attachment created from the token, instead of a link?

PK
Oct 17, 2014 at 7:17 AM
Or maybe there could be some sort of clean-up module that just registers all files not present in Media Library, that are present in the OS Media folder?
The upgrade module has this under the Media 1.7 tab
Oct 17, 2014 at 7:20 AM
@EmadMokhtar added you to the module the other day.
Oct 17, 2014 at 9:42 AM
I think it would be a good idea to register the uploads in the MediaLibrary (e.g. make use of the MediaLibrary service).
That way for each uploaded media item it will be registered as a media content, and therefore show up in the content items and being queryable.
Oct 22, 2014 at 7:28 AM
sebastienros wrote:
Please consider making these kind of field part of the new dynamic forms module from Sipke (in the dynamicforms branch). The module targets specifically the front end, and will let users create forms dynamically. The content can then be bound to internal content items if necessary.
I couldn't find the branch
Oct 22, 2014 at 7:28 AM
paulking wrote:
Great work gents. This is essential field for a number of my clients.

PK
I'm glad it helps :)
Developer
Oct 22, 2014 at 7:37 AM
EmadMokhtar wrote:
I couldn't find the branch
The branch was merged into the 1.x branch recently. The feature/dynamicforms branch has been deleted.
Oct 22, 2014 at 7:44 AM
jrmurdoch wrote:
@EmadMokhtar added you to the module the other day.
Thanks :)
Oct 22, 2014 at 1:29 PM
paulking wrote:
Emad,

Would I be stretching the friendship if I asked if its possible to register the upload with Media Library Picker?

Or maybe there could be some sort of clean-up module that just registers all files not present in Media Library, that are present in the OS Media folder?

Also, Workflows Email, could there possibly be an attachment created from the token, instead of a link?

PK
Actually I'm new with Orchard, but I'll think about your suggestions.
Oct 22, 2014 at 1:30 PM
Folks,

Any book or any reference for Orchard APIs and development?
Oct 22, 2014 at 1:43 PM
Hi Emad,

plural site is your best option

PK