/// <summary> /// Internal method that performs execution of actions on controllers (usually triggered by Controller.Action()) /// </summary> /// <param name="requestContext">Executing request context</param> protected virtual void Execute(RequestContext requestContext) { var oldContext = RequestContext; try { RequestContext = requestContext; requestContext.ProcessingController = this; var methodName = "index"; if (requestContext.RouteData.Data.ContainsKey("action")) methodName = requestContext.RouteData.Data["action"].ToString(); else requestContext.RouteData.Data.Add("action", methodName); var routedParameters = requestContext.RouteData.Data.Keys.Where(key => key != "controller" && key != "action").ToDictionary(key => key, key => requestContext.RouteData.Data[key].GetType()); var methods = GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance); foreach (var method in methods) if (method.Name.ToLower() == methodName.ToLower() && (method.ReturnType == typeof (ActionResult) || method.ReturnType.IsSubclassOf(typeof (ActionResult)))) { // Potential match! var paras = new List<object>(); var expectedParameters = method.GetParameters(); if (expectedParameters.Length != routedParameters.Count) continue; // We look at each parameter this method expects and see if we have them in the route info foreach (var expectedParameter in expectedParameters) { var expectedParameterName = expectedParameter.Name.ToLower(); if (routedParameters.ContainsKey(expectedParameterName) && expectedParameter.ParameterType == routedParameters[expectedParameterName]) paras.Add(requestContext.RouteData.Data[expectedParameterName]); } if (paras.Count == expectedParameters.Length) // Looks like we found values for all parameters { requestContext.Result = method.Invoke(this, paras.ToArray()) as ActionResult; return; // We found and invoked the method, so we are done } } // We haven't found the desired method yet, but we attempt another way of matching it if (routedParameters.Count == 1) foreach (var method in methods) if (method.Name.ToLower() == methodName.ToLower() && (method.ReturnType == typeof (ActionResult) || method.ReturnType.IsSubclassOf(typeof (ActionResult)))) { // Potential match! var expectedParameters = method.GetParameters(); if (expectedParameters.Length != 1) continue; if (expectedParameters[0].ParameterType != routedParameters.First().Value) continue; // We have a single-parameter method with a parameter type match, so we use that var paras = (from key in requestContext.RouteData.Data.Keys where key != "controller" && key != "action" select requestContext.RouteData.Data[key]).ToList(); requestContext.Result = method.Invoke(this, paras.ToArray()) as ActionResult; return; // We found and invoked the method, so we are done } // Since we got to this point, we weren't able to find the desired action, so we indicate this to the caller requestContext.Result = null; var exceptionMessage = "The action '" + methodName + "'"; if (routedParameters.Count > 0) { exceptionMessage += " with parameter(s) named '"; var parameterList = string.Empty; foreach (var parameterKey in routedParameters.Keys) { if (!string.IsNullOrEmpty(parameterList)) parameterList += ", "; parameterList += parameterKey; } exceptionMessage += parameterList; exceptionMessage += "'"; } exceptionMessage += " on controller '" + GetType().Name + "' was not found."; throw new ActionNotFoundException(exceptionMessage); } finally { RequestContext = oldContext; } }
/// <summary> /// Executes all the registered view handlers based on the current context /// </summary> /// <param name="context"></param> private static void ExecuteViewHandlers(RequestContext context) { if (_viewHandlers != null) { var currentHandlers = _viewHandlers.Values.ToList(); // We get a new list of handlers, because it could actually happen that calling Openview() on a handler registers handlers and thus change the _viewHandlers collection foreach (var handler in currentHandlers) handler.OpenView(context); var viewResult = context.Result as ViewResult; if (viewResult != null) viewResult.RaiseViewOpened(); } }
/// <summary>Finds a view within the context of the specified controller</summary> /// <param name="controller">Controller the action is to be triggered on</param> /// <param name="view">Name of the view that is to be found</param> /// <returns>Request context (can be ignored except for special cases)</returns> public static RequestContext ViewOnly(string controller = "Home", string view = "Index") { if (!controller.EndsWith("Controller")) controller += "Controller"; if (_controllers == null) PopulateControllers(); var controllerKey = controller.ToLower(); if (_controllers != null && _controllers.ContainsKey(controllerKey)) { var context = new RequestContext(new RouteData(view, null)) { Result = _controllers[controllerKey].Instance.PartialView(view) }; if (_viewHandlers != null) { var currentHandlers = _viewHandlers.Values.ToList(); // We get a new list of handlers, because it could actually happen that calling Openview() on a handler registers handlers and thus change the _viewHandlers collection foreach (var handler in currentHandlers) handler.OpenView(context); var viewResult = context.Result as ViewResult; if (viewResult != null) viewResult.RaiseViewOpened(); } return context; } return null; }
/// <summary>Displays a message box</summary> /// <param name="messageBoxText">Message box text message (plain text)</param> /// <param name="caption">Message box caption (title)</param> /// <param name="buttons">Standard buttons displayed by the message box</param> /// <param name="icon">Standard icon displayed by the message box.</param> /// <param name="defaultResult">Default standard button</param> /// <param name="onComplete">Code to run when the message box closes.</param> /// <param name="actions">Custom actions to be added to the message box as buttons. (Note: If this parameter is not null, the 'buttons' parameter is ignored)</param> /// <param name="model">Custom message box view model. (Note: Only used in exceptional scenarios where the standard view model .</param> /// <param name="viewName">Name of a custom view to be used by the message box (optional).</param> /// <param name="controllerType">Type of the controller (used as a context to find views)</param> public static void Message(string messageBoxText = "", string caption = "Message", MessageBoxButtons buttons = MessageBoxButtons.OK, MessageBoxImages icon = MessageBoxImages.Information, MessageBoxResults defaultResult = MessageBoxResults.OK, Action<MessageBoxResult> onComplete = null, IEnumerable<IViewAction> actions = null, MessageBoxViewModel model = null, string viewName = "", Type controllerType = null) { var context = new RequestContext(new RouteData("MessageBox", new {viewName = string.Empty, messageBoxText, caption, buttons, icon, defaultResult})); // If a controller type was specified, we try to use it, which provides a context to find views Controller controller; if (controllerType == null) controller = new Controller(); else controller = Activator.CreateInstance(controllerType) as Controller; if (controller == null) controller = new Controller(); context.ProcessingController = controller; if (model != null && onComplete != null) model.OnComplete = onComplete; // Could be used later, especially for test scenarios where queued results simulate closing of message boxes bool mustHandle = _messageBoxResultQueue == null || _messageBoxResultQueue.Count == 0; context.Result = controller.MessageBox(viewName, messageBoxText, caption, buttons, icon, defaultResult, actions, model); var result = context.Result as MessageBoxResult; if (result != null && onComplete != null) result.ViewClosed += (o, e) => onComplete(result); if (mustHandle) // By default, we let view handlers handle the message box, unless simulated results are queued up. ExecuteViewHandlers(context); }
/// <summary> /// Triggers an action on the specified controller /// </summary> /// <param name="controller">Controller the action is to be triggered on</param> /// <param name="action">Action to be called on the controller</param> /// <param name="routeValues">Additional parameters passed to the controller</param> /// <param name="eventSinks">Object containing the event handlers that are to be attached to the view result (if the result is in fact a view result)</param> /// <returns>Request context (can be ignored except for special cases)</returns> /// <example> /// Controller.Action("Customer", "List"); // Invokes CustomerController.List /// var context = Controller.Action("Customer", "List"); // Invokes CustomerController.List and retrieves the context /// Controller.Action("Customer", "Detail", new {id = x}); // Invokes CustomerController.Detail(id) and passes a parameter called "id") /// </example> public static RequestContext Action(string controller = "Home", string action = "Index", dynamic routeValues = null, ViewResultEventSinks eventSinks = null) { if (!controller.EndsWith("Controller")) controller += "Controller"; if (_controllers == null) PopulateControllers(); var controllerKey = controller.ToLower(); if (_controllers != null && _controllers.ContainsKey(controllerKey)) { var context = new RequestContext(new RouteData(action, routeValues)); _controllers[controllerKey].Instance.Execute(context); var viewResult = context.Result as ViewResult; if (viewResult != null) { if (eventSinks != null) { viewResult.ViewClosed += eventSinks.ViewClosed; viewResult.ViewOpened += eventSinks.ViewOpened; } viewResult.ViewOpened += (o, e) => AfterViewOpened(viewResult); var openable = viewResult.Model as IOpenable; if (openable != null) openable.RaiseOpeningEvent(); } ExecuteViewHandlers(context); if (viewResult != null) { var openable = viewResult.Model as IOpenable; if (openable != null) openable.RaiseOpenedEvent(); } return context; } return null; }
/// <summary>Displays the specified notification</summary> /// <param name="text">Main text</param> /// <param name="text2">Secondary text</param> /// <param name="number">Numeric information (such as an item count) passed as a string</param> /// <param name="imageResource">Generic image resource to load a brush from (if this parameter is passed an the resource is found the image parameter is ignored)</param> /// <param name="image">A logo image (passed as a brush).</param> /// <param name="model">Notification view model (if passed, text, number, image and overrideTimeout parameters are ignored)</param> /// <param name="viewName">Name of a custom view to be used by the status.</param> /// <param name="controllerType">Type of the controller (used as a context to find views)</param> /// <param name="overrideTimeout">Overrides the theme's default notification timeout. If model is passed, set this property in model.</param> public static void Notification(string text = "", string text2 = "", string number = "", string imageResource = "", Brush image = null, NotificationViewModel model = null, string viewName = "", Type controllerType = null, TimeSpan? overrideTimeout = null) { var context = new RequestContext(new RouteData("NotificationMessage", new {viewName = string.Empty})); // If a controller type was specified, we try to use it, which provides a context to find views Controller controller; if (controllerType == null) controller = new Controller(); else controller = Activator.CreateInstance(controllerType) as Controller; if (controller == null) controller = new Controller(); context.ProcessingController = controller; context.Result = controller.NotificationMessage(viewName, text, text2, number, imageResource, image, model, overrideTimeout); ExecuteViewHandlers(context); }
/// <summary>Sets the application status (typically displayed in a status bar).</summary> /// <param name="message">Message that is to be displayed</param> /// <param name="status">Application status</param> /// <param name="model">Application status view model</param> /// <param name="viewName">Name of a custom view to be used by the status.</param> /// <param name="controllerType">Type of the controller (used as a context to find views)</param> public static void Status(string message = "", ApplicationStatus status = ApplicationStatus.Ready, StatusViewModel model = null, string viewName = "", Type controllerType = null) { var context = new RequestContext(new RouteData("StatusMessage", new {viewName = string.Empty})); // If a controller type was specified, we try to use it, which provides a context to find views Controller controller; if (controllerType == null) controller = new Controller(); else controller = Activator.CreateInstance(controllerType) as Controller; if (controller == null) controller = new Controller(); context.ProcessingController = controller; context.Result = controller.StatusMessage(viewName, message, status, model); ExecuteViewHandlers(context); }
/// <summary> /// Opens the top level view in a separate window. /// </summary> /// <param name="context">The context.</param> /// <param name="messageBoxResult">The message box result.</param> /// <param name="viewResult">The view result.</param> /// <returns>True if successfully opened</returns> private bool OpenTopLevelView(RequestContext context, MessageBoxResult messageBoxResult, ViewResult viewResult) { if (messageBoxResult != null && string.IsNullOrEmpty(viewResult.ViewIconResourceKey)) viewResult.ViewIconResourceKey = messageBoxResult.ModelMessageBox.IconResourceKey; //Brush iconBrush = Brushes.Transparent; //if (!string.IsNullOrEmpty(viewResult.ViewIconResourceKey)) // try // { // var resource = Application.Current.FindResource(viewResult.ViewIconResourceKey); // if (resource != null) // iconBrush = (Brush) resource; // } // catch // { // iconBrush = Brushes.Transparent; // } // If we respect local views and the view is in fact a local view, and we have a normal view already open, then we open it in a local way only. if (viewResult.ViewScope == ViewScope.Local && HandleLocalViewsSpecial && SelectedNormalView > -1) { var selectedView = NormalViews[SelectedNormalView]; if (selectedView == null) return false; selectedView.LocalViews.Add(viewResult); if (viewResult.MakeViewVisibleOnLaunch) selectedView.SelectedLocalViewIndex = selectedView.LocalViews.Count - 1; return true; } //Need to make sure we do not open more than allowed - Popups should not close underlying views. if (viewResult.ViewLevel != ViewLevel.Popup && MaximumTopLevelViewCount > -1) { var inplaceTopLevelviews = TopLevelViews.Where(v => v.TopLevelWindow == null).ToList(); while (inplaceTopLevelviews.Count + 1 > MaximumTopLevelViewCount) { CloseViewForModel(inplaceTopLevelviews[0].Model); inplaceTopLevelviews.RemoveAt(0); } } TopLevelViews.Add(viewResult); if (viewResult.MakeViewVisibleOnLaunch && !(TopLevelViewLaunchMode == ViewLaunchMode.Popup || (TopLevelViewLaunchMode == ViewLaunchMode.InPlaceExceptPopups && viewResult.ViewLevel == ViewLevel.Popup))) { SelectedTopLevelView = TopLevelViews.Count - 1; SelectedTopLevelViewResult = SelectedTopLevelView > -1 ? TopLevelViews[SelectedTopLevelView] : null; if (viewResult.View != null) if (!FocusHelper.FocusFirstControlDelayed(viewResult.View)) FocusHelper.FocusDelayed(viewResult.View); } TopLevelViewCount = TopLevelViews.Count; if (TopLevelViewLaunchMode == ViewLaunchMode.Popup || (TopLevelViewLaunchMode == ViewLaunchMode.InPlaceExceptPopups && viewResult.ViewLevel == ViewLevel.Popup)) { var window = new Window { Title = viewResult.ViewTitle, Content = viewResult.View, DataContext = viewResult.Model, WindowStartupLocation = WindowStartupLocation.CenterScreen, Owner = this }; window.SetBinding(TitleProperty, new Binding("ViewTitle") { Source = viewResult }); // Setting the size strategy var strategy = SimpleView.GetSizeStrategy(viewResult.View); switch (strategy) { case ViewSizeStrategies.UseMinimumSizeRequired: window.SizeToContent = SizeToContent.WidthAndHeight; break; case ViewSizeStrategies.UseMaximumSizeAvailable: window.SizeToContent = SizeToContent.Manual; window.Height = SystemParameters.WorkArea.Height; window.Width = SystemParameters.WorkArea.Width; break; case ViewSizeStrategies.UseSuggestedSize: window.SizeToContent = SizeToContent.Manual; window.Height = SimpleView.GetSuggestedHeight(viewResult.View); window.Width = SimpleView.GetSuggestedWidth(viewResult.View); break; } viewResult.TopLevelWindow = window; if (context.Result is MessageBoxResult) window.SetResourceReference(StyleProperty, "CODE.Framework.Wpf.Mvvm.Shell-TopLevelMessageBoxWindowStyle"); else window.SetResourceReference(StyleProperty, "CODE.Framework.Wpf.Mvvm.Shell-TopLevelWindowStyle"); if (viewResult.View != null) foreach (InputBinding binding in viewResult.View.InputBindings) window.InputBindings.Add(binding); if (!FocusHelper.FocusFirstControlDelayed(window)) FocusHelper.FocusDelayed(window); if (viewResult.IsModal) window.ShowDialog(); else window.Show(); } //if (iconBrush != null) //{ // try // { // // TODO: Implement the icon logic // //var iconRect = new Canvas {Height = 96, Width = 96, Background = iconBrush}; // //window.Icon = iconRect.ToIconSource(); // } // catch // { // } //} return true; }
/// <summary> /// Opens a normal view in a separate window. /// </summary> /// <param name="context">The context.</param> /// <param name="viewResult">The view result.</param> private static void OpenNormalViewInWindow(RequestContext context, ViewResult viewResult) { var window = new Window { Title = viewResult.ViewTitle, SizeToContent = SizeToContent.WidthAndHeight, Content = viewResult.View, DataContext = viewResult.Model, WindowStartupLocation = WindowStartupLocation.CenterScreen }; window.SetBinding(TitleProperty, new Binding("ViewTitle") {Source = viewResult}); var simpleView = viewResult.View as SimpleView; if (simpleView != null) if (SimpleView.GetSizeStrategy(simpleView) == ViewSizeStrategies.UseMaximumSizeAvailable) window.SizeToContent = SizeToContent.Manual; viewResult.TopLevelWindow = window; if (context.Result is MessageBoxResult) window.SetResourceReference(StyleProperty, "CODE.Framework.Wpf.Mvvm.Shell-TopLevelMessageBoxWindowStyle"); else window.SetResourceReference(StyleProperty, "CODE.Framework.Wpf.Mvvm.Shell-NormalLevelWindowStyle"); if (viewResult.IsModal) window.ShowDialog(); else window.Show(); }
/// <summary> /// This method is invoked when a view is opened /// </summary> /// <param name="context">Request context (contains information about the view)</param> /// <returns> /// True if handled successfully /// </returns> public bool OpenView(RequestContext context) { if (context.Result is StatusMessageResult) return HandleStatusMessage(context); if (context.Result is NotificationMessageResult) { var result = context.Result as NotificationMessageResult; return HandleNotificationMessage(context, result.Model.OverrideTimeout); } var viewResult = context.Result as ViewResult; if (viewResult != null && viewResult.ForceNewShell) if ((viewResult.ViewLevel == ViewLevel.Normal && NormalViewCount > 0) || (viewResult.ViewLevel == ViewLevel.TopLevel && TopLevelViewCount > 0)) { // A new shell should be opened, so we try to create another shell and then hand off view opening duties to it, rather than handling it directly var shellLauncher = Controller.GetShellLauncher() as WindowShellLauncher<Shell>; if (shellLauncher != null) if (shellLauncher.OpenAnotherShellInstance()) return Current.OpenView(context); } var messageBoxResult = context.Result as MessageBoxResult; if (messageBoxResult != null) { if (messageBoxResult.View == null) { var messageBoxViewModel = messageBoxResult.Model as MessageBoxViewModel; if (messageBoxViewModel != null) { var textBlock = new TextBlock {TextWrapping = TextWrapping.Wrap, Text = messageBoxViewModel.Text}; textBlock.SetResourceReference(StyleProperty, "CODE.Framework.Wpf.Mvvm.Shell-MessageBox-Text"); if (!string.IsNullOrEmpty(messageBoxViewModel.IconResourceKey)) { var grid = new Grid(); grid.ColumnDefinitions.Clear(); grid.ColumnDefinitions.Add(new ColumnDefinition {Width = GridLength.Auto}); grid.ColumnDefinitions.Add(new ColumnDefinition {Width = new GridLength(1, GridUnitType.Star)}); var rect = new Rectangle {Fill = messageBoxViewModel.IconBrush}; rect.SetResourceReference(StyleProperty, "CODE.Framework.Wpf.Mvvm.Shell-MessageBox-Image"); grid.Children.Add(rect); grid.Children.Add(textBlock); Grid.SetColumn(textBlock, 1); SimpleView.SetSizeStrategy(grid, ViewSizeStrategies.UseMinimumSizeRequired); messageBoxResult.View = grid; } else messageBoxResult.View = textBlock; } } if (messageBoxResult.View != null) { var messageBoxModelAction = messageBoxResult.Model as IHaveActions; if (messageBoxModelAction != null) { messageBoxResult.View.InputBindings.Clear(); foreach (var action in messageBoxModelAction.Actions) if (action.ShortcutKey != Key.None) messageBoxResult.View.InputBindings.Add(new KeyBinding(action, action.ShortcutKey, action.ShortcutModifiers)); } } } if (viewResult != null && viewResult.View != null && !viewResult.IsPartial) { if (viewResult.ViewLevel == ViewLevel.Popup || viewResult.ViewLevel == ViewLevel.StandAlone || viewResult.ViewLevel == ViewLevel.TopLevel) return OpenTopLevelView(context, messageBoxResult, viewResult); // This is an normal (main) view // Need to make sure we do not open more than allowed if (MaximumNormalViewCount > -1) { var inplaceNormalViews = NormalViews.Where(v => v.TopLevelWindow == null).ToList(); while (inplaceNormalViews.Count + 1 > MaximumNormalViewCount) { CloseViewForModel(inplaceNormalViews[0].Model); inplaceNormalViews.RemoveAt(0); } } NormalViews.Add(viewResult); if (viewResult.MakeViewVisibleOnLaunch) { FocusManager.SetFocusedElement(this, viewResult.View); SelectedNormalView = NormalViews.Count - 1; SelectedNormalViewResult = SelectedNormalView > -1 ? NormalViews[SelectedNormalView] : null; } NormalViewCount = NormalViews.Count; if (NormalViewLaunchMode == ViewLaunchMode.Popup) OpenNormalViewInWindow(context, viewResult); return true; } return false; }
/// <summary>Handles opening of views that are notification messages</summary> /// <param name="context">The request context.</param> /// <param name="overrideTimeout">Overrides the theme's default notification timeout.</param> /// <returns>True of view was handled</returns> protected virtual bool HandleNotificationMessage(RequestContext context, TimeSpan? overrideTimeout = null) { var notificationResult = context.Result as NotificationMessageResult; if (notificationResult == null) return false; var wrapper = new NotificationViewResultWrapper {Model = notificationResult.Model}; if (notificationResult.View != null) { wrapper.View = notificationResult.View; notificationResult.View.DataContext = wrapper.Model; } else { wrapper.View = Controller.LoadView(StandardViews.Notification); if (wrapper.View != null) wrapper.View.DataContext = wrapper.Model; } if (NotificationSort == NotificationSort.NewestFirst) CurrentNotifications.Add(wrapper); else CurrentNotifications.Insert(0, wrapper); while (CurrentNotifications.Count > MaximumNotificationCount) { // Handling this like a stack, popping the oldest one off if (NotificationSort == NotificationSort.NewestFirst) CurrentNotifications.RemoveAt(0); else CurrentNotifications.RemoveAt(CurrentNotifications.Count - 1); } CurrentNotificationsCount = CurrentNotifications.Count; RaiseEvent(new RoutedEventArgs(NotificationChangedEvent)); var timeout = overrideTimeout ?? NotificationTimeout; wrapper.InternalTimer = new Timer(state => Application.Current.Dispatcher.BeginInvoke(new Action(() => { CurrentNotifications.Remove(wrapper); CurrentNotificationsCount = CurrentNotifications.Count; })), null, timeout, new TimeSpan(-1)); return true; }
/// <summary>Handles opening of views that are status messages</summary> /// <param name="context">The request context.</param> /// <returns>True of view was handled</returns> protected virtual bool HandleStatusMessage(RequestContext context) { var statusResult = context.Result as StatusMessageResult; if (statusResult == null) return false; if (Status == null) Status = new StatusViewResultWrapper(); Status.Model = statusResult.Model; if (statusResult.View != null) { Status.View = statusResult.View; var element = Status.View as FrameworkElement; if (element != null) element.DataContext = Status.Model; } else { var grid = new Grid(); var text = new TextBlock(); grid.Children.Add(text); var binding = new Binding("Message"); text.SetBinding(TextBlock.TextProperty, binding); grid.DataContext = Status.Model; Status.View = grid; } CurrentStatusView = Status.View; CurrentApplicationStatus = Status.Model.Status; RaiseEvent(new RoutedEventArgs(StatusChangedEvent)); return true; }