Open dodyg opened 4 years ago
Or more importantly does the workflow support file upload?
There is no support for file upload through workflows right now.
Related issues https://github.com/OrchardCMS/OrchardCore/issues/4896
OK - let me try to implement a task for the workflow that handle a file upload.
I am trying to write a simple Activity first just to get a hang on this custom activity creation thing. This one will write a message to the disk when it is done.
This thumbnail is handled by DiskWriterTask.Fields.Thumbnail.cshtml.
This is a good simple activity task to learn from https://github.com/EtchUK/Etch.OrchardCore.Workflows/tree/master/FormOutput
If anyone need similar functionality, this code will work
public class DiskWriterTask : TaskActivity
{
readonly IOptions<ShellOptions> _shellOptions;
readonly IStringLocalizer S;
readonly ShellSettings _shellSettings;
private readonly IHttpContextAccessor _http;
public DiskWriterTask(IStringLocalizer<DiskWriterTask> s,
IOptions<ShellOptions> shellOptions,
ShellSettings shellSettings,
IHttpContextAccessor httpContextAccessor)
{
_shellOptions = shellOptions;
_shellSettings = shellSettings;
S = s;
_http = httpContextAccessor;
}
public override string Name => nameof(DiskWriterTask);
public override LocalizedString DisplayText => S["Disk Writer Task"];
public override LocalizedString Category => S["UI"];
public override IEnumerable<Outcome> GetPossibleOutcomes(WorkflowExecutionContext workflowContext, ActivityContext activityContext)
{
return Outcomes(S["Done"]);
}
public override bool CanExecute(WorkflowExecutionContext workflowContext, ActivityContext activityContext)
{
return _http.HttpContext?.Request?.Form != null;
}
public override async Task<ActivityExecutionResult> ExecuteAsync(WorkflowExecutionContext workflowContext, ActivityContext activityContext)
{
var shell = _shellOptions.Value;
var directory = PathExtensions.Combine(shell.ShellsApplicationDataPath, shell.ShellsContainerName, _shellSettings.Name, Folder);
if (!Directory.Exists(directory))
Directory.CreateDirectory(directory);
foreach(var file in _http.HttpContext.Request.Form.Files)
{
var toSave = PathExtensions.Combine(directory, file.FileName);
using (var stream = System.IO.File.Create(toSave))
{
await file.CopyToAsync(stream);
workflowContext.Properties[file.Name] = toSave;
}
}
return Outcomes("Done");
}
public string Folder
{
get => GetProperty<string>();
set => SetProperty(value ?? string.Empty);
}
}
public class DiskWriterViewModel
{
public string Folder { get; set; }
}
public class DiskWriterTaskDisplay: ActivityDisplayDriver<DiskWriterTask, DiskWriterViewModel>
{
protected override void EditActivity(DiskWriterTask activity, DiskWriterViewModel model)
{
model.Folder = activity.Folder;
}
protected override void UpdateActivity(DiskWriterViewModel model, DiskWriterTask activity)
{
activity.Folder = model.Folder;
}
}
You will have to fill the rest of the views.
This is just a ContentItem with FlowPart
This is how it renders
Because the Form widget doesn't support multiple, you have to modify the generated html with Javascript
$(function(){
$('form').attr("enctype","multipart/form-data");
});
This is the property page of the DiskWriter activity. The one I added is just Folder. The Tittle input comes by default.
This is my first time writing a custom activity task so this one is gonna be rough at the edges.
Simple worklflow that will add data from the form
Pay attention to this one. There are older samples that uses JSON.parse(readBody())
but that stopped working a while ago. The name of the property here will be accessible as Workflow.Properties.YourPropertyName
. So in this case, this property can be access as Workflow.Properties.ApplicationDetails
in Liquid.
This part is the one that insert the data from the form to your content.
@dodyg can upload your solution ?
There you go. Disk Writer.zip
You need to modify the code to make it more robust but all the necessary ingredients are there.
@dodyg thanks.
Thanks a lot @dodyg .. very detailed and useful explanation as usual 🥇
@dodyg I updated your Task to include liquid support on the folder.
I've done something similar around two months ago and maybe I'm allowed to add some thoughts about security and validation. In my case, I've added some more fields to the file upload activity and some more output options:
Fields:
Optional fields:
Feature field:
I think the save part needs to change so if someone is using cloud blob storage then it would also work. Seems like its hardcoded to the local filesystem.
@stevetayloruk Yup. This is just a quick and dirty implementation for my local project purpose.
What about having such a task by default in the project? I assume we would need to use a custom abstraction of the FS, such that we could write to a custom folder or store (blob). These files should not be servable by default. Or have an option to store them in a specific media folder, if we want them to be servable.
Maybe it should only use the media service. And we'd need to have a way to use a custom folder that can't serve files publicly. But we'd be able to browse them from the admin.
@sebastienros This would be really great. I mean, I'm currently working with my own solution as described, but it's far from perfect and not for everyones needs. In addition to that, there is a very common scenario, where people are attaching files to a form: a job application. With that, you have two Options to handle the files: 1.) Store them inside the media/file system 2.) Send them by mail with a workflow
In case of option 2, I've extended the OrchardCore.Email module to support attachments which would be really helpfull as default functionality. My file upload task sets the paths as workflow variables which the extended email module reads and attaches them to the mail. This could be solved way better, but it works for now.
The ability to choose wheter a media folder is public or private would be awesome, because I would really like to store files inside the media, that are only available through the admin panel, so I don't have to save my files outside of the media folder with a hardcoded path anymore.
@sebastienros Is there any plans to have media folders/files securable through the permission system?
What would your approach be to achieve this?
Per role, per folder:
In the attached media field, we already do that, by forcing a custom folder, and not showing other ones.
Bonus feature (harder):
I think it should be done at the folder level to simplify the UI.
The Form widget now supports multipart/form-data
. If https://github.com/OrchardCMS/OrchardCore/pull/12218 is merged, we'll have file uploads in Workflows too.
The most recent conversation here changed over to authorized Media (file storage) access though. For that, we have this other issue: https://github.com/OrchardCMS/OrchardCore/issues/3590.
I didn't see anything in the Workflow nor the Form widget support for "multipart/form-data"