3
\$\begingroup\$

I'm developing a webapp in ASP.Net MVC. One of the requirements of the project is that users follow a "workflow". The workflow has a number of steps, and a conditional branch. The workflow is long running (week+) and most steps rely on one or more API calls to a 3rd party.

I have previously wondered about application "flow" in a typical webapp. Say for instance that every workflow step has a corrisponding URL eg: /Workflow/Step1, /Workflow/Step2. In this particular workflow, Step1 is not valid once you reach Step2.

I read about Application Controller patterns and decided to try and make a basic workflow object, where the viewstate is managed by the workflow, rather than the particular endpoint.

This would mean that for instance, visiting /Workflow/Process would return the current stage of the workflow, rather than a single view. There's a custom model binder which can take a namespace and bind to a model as long as there's a name for it in the form collection.

This is a template of the controller. It's a work in progress, but the idea is that additional workflows would be pluggable from the business layer, rather then relying on links between views. Is this a terrible idea, and can anyone see any fundamental problems with this approach?

public class CaseController : Controller
{
    public ActionResult New()
    {
        var store = new InMemoryCaseStore<ExampleCase>();
        var workflow = new ExampleCaseWorkflow(store);
        var result = workflow.New();

        return View(result.ViewName, result.ViewModel);
    }

    public ActionResult Load(string Id)
    {
        var store = new InMemoryCaseStore<ExampleCase>();
        var workflow = new ExampleCaseWorkflow(store);
        var result = workflow.Load(Id);

        return View(result.ViewName, result.ViewModel);
    }

    public ActionResult Process([NamespaceModelBinder("Business.Models")]dynamic model)
    {
        var temp = model.Id;

        var store = new InMemoryCaseStore<ExampleCase>();
        var workflow = new ExampleCaseWorkflow(store);
        var stage = workflow.Next(model);

        return View(stage.ViewName, stage.ViewModel);
    }

    public ActionResult Back(string Id)
    {
        throw new NotImplementedException();
    }
}


public class NamespaceModelBinder : DefaultModelBinder
{
    public NamespaceModelBinder(string location)
    {
        //todo: this is going to be slow - test and see how bad it is.
        var q = from t in AppDomain.CurrentDomain.GetAssemblies().SelectMany(t => t.GetTypes())
                where t.IsClass && t.Namespace == location
                select t;
        modelMap = q.ToDictionary(x => x.Name, x => x);
    }

    Dictionary<string, Type> modelMap;

    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {

        var type = controllerContext.HttpContext.Request.Form["ModelType"];
        bindingContext.ModelName = type;
        bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, modelMap[type]);

        return base.BindModel(controllerContext, bindingContext);
    }
}
\$\endgroup\$
  • \$\begingroup\$ AppDomain.CurrentDomain.GetAssemblies() could be very problematic. First, there are a lot of system stuff you would not like to touch because of possible security issues (exceptions could happen). Second, you will need to ensure somehow that you loaded all the referenced assemblies of interest before executing this code. Probably good old IoC container with multiple ViewModel IHandler<T>.Handle(T model) implementations could work best here to help dispatch your models. Do you have an IoC container in your project? \$\endgroup\$ – Dmitry Nogin Sep 10 '16 at 2:42
  • \$\begingroup\$ Agreed, I'll switch to an IoC container, the assembly scanner was only really a temporary fix. \$\endgroup\$ – Alex Sep 12 '16 at 8:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.