using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace System.Web.Mvc { /// /// Provides the right action method template for action method. /// /// ViewModel class that in used in this action method and related view. public class ActionTemplate { /// /// Initializes a new instance of ActionTemplate class. /// /// The controller. /// If true then if some exception occurs during ViewModel initialization or update then this exception is added into ModelState. public ActionTemplate(ControllerBase controller, bool saveExceptionsToModelState) { this.Controller = controller; if (saveExceptionsToModelState) { this.HandleInitializeViewModelException = (viewModel, exception) => { this.Controller.ViewData.ModelState.AddModelError("error", exception); return null; }; this.HandleUpdateViewModelException = (viewModel, exception) => { this.Controller.ViewData.ModelState.AddModelError("error", exception); return null; }; } } /// /// The controller. /// public ControllerBase Controller { get; set; } #region initialization /// /// Creates ViewModel instance. This would probably done using action method parameters. If null is specified then ViewModel will have its default value. /// After model is created then it's assigned to ViewData.Model property. /// public Func InitializeViewModel { get; set; } /// /// If non-null is specified then catches all exceptions during ViewModel initialization. /// If specified delegate returns non-null action result then current action method is terminated and this action result is used as return value. /// public Func HandleInitializeViewModelException { get; set; } /// /// If some delegate is specified then allows to handle invalid ModelState after ViewModel initialization. /// If specified delegate returns non-null action result then current action method is terminated and this action result is used as return value. /// public Func HandleInvalidViewModelAfterInitialization { get; set; } #endregion #region update /// /// Updates ViewModel. /// public Action UpdateViewModel { get; set; } /// /// If non-null is specified then catches all exceptions during ViewModel update. /// If specified delegate returns non-null action result then current action method is terminated and this action result is used as return value. /// public Func HandleUpdateViewModelException { get; set; } /// /// If some delegate is specified then allows to handle invalid ModelState after ViewModel update. /// If specified delegate returns non-null action result then current action method is terminated and this action result is used as return value. /// public Func HandleInvalidViewModelAfterUpdate { get; set; } #endregion #region result /// /// Delegate that returns action result that will be returned from action method. If null then null is returned. /// public Func ObtainActionResult { get; set; } /// /// If non-null is specified then catches all exceptions during action result obtaining. /// If some exception occurs then action method returns value that delegate returns. /// public Func HandleObtainActionResultException { get; set; } #endregion /// /// Runs delegates specified as properties in the right order. /// /// Returns action result. public virtual ActionResult Run() { if (this.Controller == null) { throw new InvalidOperationException("Controller property must be set before Run method calling."); } var viewModel = default(TViewModel); // ViewModel initialization if (HandleInitializeViewModelException != null) { try { if (InitializeViewModel != null) { viewModel = InitializeViewModel(); } } catch (Exception e) { var ar = HandleInitializeViewModelException(viewModel, e); if (ar != null) { return ar; } } } else { if (InitializeViewModel != null) { viewModel = InitializeViewModel(); } } // set view-ViewModel (but not rewrite) if (Controller.ViewData != null && Controller.ViewData.Model == null) { Controller.ViewData.Model = viewModel; } // post-initialize check if (!Controller.ViewData.ModelState.IsValid && HandleInvalidViewModelAfterInitialization != null) { var res = HandleInvalidViewModelAfterInitialization(viewModel); if (res != null) { return res; } } // ViewModel updating if (HandleUpdateViewModelException != null) { try { if (UpdateViewModel != null) { UpdateViewModel(viewModel); } } catch (Exception e) { var ar = HandleUpdateViewModelException(viewModel, e); if (ar != null) { return ar; } } } else { if (UpdateViewModel != null) { UpdateViewModel(viewModel); } } // post-update check if (HandleInvalidViewModelAfterUpdate != null) { var res = HandleInvalidViewModelAfterUpdate(viewModel); if (res != null) { return res; } } if (HandleObtainActionResultException != null) { try { return ObtainActionResult != null ? ObtainActionResult(viewModel) : null; } catch (Exception e) { return HandleObtainActionResultException(viewModel, e); } } else { return ObtainActionResult != null ? ObtainActionResult(viewModel) : null; } } } /// /// Provides the right action method template for action method. /// The ViewModel type must have parameterless constructor. This constructor is called by automatically generated InitializeViewModel delegate. /// /// ViewModel class that in used in this action method and related view. public class ActionTemplateWithInstantiableViewModel : ActionTemplate where TViewModel : new() { /// /// Initializes a new instance of ActionTemplateWithInstantiableViewModel class. /// /// The controller. /// If true then if some exception occurs during ViewModel initialization or update then this exception is added into ModelState. public ActionTemplateWithInstantiableViewModel(ControllerBase controller, bool saveExceptionsToModelState) : base(controller, saveExceptionsToModelState) { this.InitializeViewModel = () => new TViewModel(); } } }