Skip to content

Delivering view models

padzikm edited this page Dec 13, 2015 · 20 revisions

When we defined widgets that will be a specific page, we want to render them. Because for each widget there is responsible one view model, so we have to create view models and deliver it to a grid view.
Because view models are created by services and grid view accept view models collection, so there must component that are responsible for gathering view models from services and pass them to grid view. As grid view is rendered from main application, which is responsible for handling request, it will be also responsible for collecting view models from services.

Publishing request

When request arrives to main application, services want to handle and possibly create view models for specific widgets. Because each widget is unique in context of given page - even if it's the same name on many pages it is unique on every single one - when we have specific page, we have widgets that should be rendered. As a result we have to find some kind of mapping between requested page and service action, so that each service knows what widgets is asked for. However this kind of mapping alread exists - it is routing, that maps incoming request into action that should handle it. So we woud like to to use routing mechanism for letting services know to create view models, instead of calling them directly.
If we could use routing, that will mean that will publish incoming request to all services, so that they can handle it in their way - we won't need to define any interface for calling services directly and mapping request on action.
Example of IRequestHandler that every service provides to publish request to it:

public interface IRequestHandler
    {
        Task HandleRequest();

        Task HandleRequest(Dictionary<string,object> parameters);

        Task HandleRequest(IEnumerable<string> uiKeys);

        Task HandleRequest(IEnumerable<string> uiKeys, Dictionary<string,object> parameters);
        
        Task<IEnumerable<IViewModel>> GenerateViewModels(IEnumerable<string> uiKeys);

        Task<IEnumerable<IViewModel>> GenerateViewModels(IEnumerable<string> uiKeys, Dictionary<string, object> parameters);

        Task<IEnumerable<IViewModel>> GenerateViewModelsOnInvalidModelState(IEnumerable<string> uiKeys);

        Task<IEnumerable<IViewModel>> GenerateViewModelsOnInvalidModelState(IEnumerable<string> uiKeys, Dictionary<string,object> parameters);
    }

There are two kinds of request handling - simply handling request on post action, and afterwards main application redirects user to different page according to post - redirect - get pattern, and delivering view models on get request.

Internal routing

Because routing is web application is one, that gets us to main application's actions, we have to define internal routing for each service. We can make an exact copy of global routing, so that when request arrives and it is published to all services, each service will get data from static HttpContext and get a route that points to service specific action. This can be achieved by changing route's namespace, because when mvc infrastructure creates controller for given route, it looks for one in given route's namespaces. So when we change namespace in each route in copy of global routing to one that points to service specific namespace, mvc infrastructure will look for controller for the same name as in main application and action the same as in main application, but in different namespace. Furthermore because widget's look can depend on parameters send in request, we want to pass this parameters to each service. However this will be done automatically, because this data is already included in static HttpContext, so for each route for each service this data will be available, as all service are integrated into one process.
As a result for each service there is created its own copy of global routing with customized namespace pointing to service namespace and when matching route is found, mvc handler is invoked to create and invoke specific action in specific controller. In effect service handler for given request will be action method, that is named the same as main application's action in controller that is named the same as main application's controller and optionally in area that is named the same as main application's area. In this way we can define service request handlers that are loosely coupled with main application and are logically consistent with incoming request.
Example of handling request:

namespace MainApplicationNamespace
{
    public class SomeController : WebController
    {
        public ActionResult SomePage(some_parameters) //main app's action for handling request
        {
             //publishing request to services
        }
    }
}

namespace ServiceApplicationNamespace
{
    public class SomeController : ServiceController
    {
        public ActionResult SomePage(some_parameters) //main app's action for handling request
        {
             //handling request in service
        }
    }
}

Data in view models

When service handles request it will either wants to just handle it, or it will want to provide view models for a page. For providing view models it will most probably create some model that will be displayed as a widget in some service view. In order to render view model we have to tell it how to render itself - we can do it by invoking Html.Action extension method on web view page, that will be rendered. We want to call action, not partial view because in partial view view data will contain view data from grid view and when Html.Action is called it will have its own view data created. So as we want to develop each service independently, we will most probably want to call Html.Action that will be converted into specific view as described in rendering view models section. We can also want to provide our service views with some view data, so we can just operate on ViewData dictionary directly in service action handler for current request and it will be copied to views that we specified in view models.
Example of createing view model:

[InternalAction]
public ActionResult SomePage(some_parameters)
{
    ViewData["someData"] = //create objects
    var someModel = //createing model
    var viewModel = new ViewModel("widgetId", page => page.Html.Action("SomeView", new { model = someModel });
    return ViewModel(viewModel);
}

Above code shows creating view model that will render content for specific widget by displaying SomeView with someModel as model passed to view and contained someData key in ViewData dictionary.

Gathering view models

When we created view models for given page in specific service we want to return them to main application. Because service actions are invoked by mvc infrastructure, we can't simply return view models objects. To solve this, when service action is complete, mvc infrastructure invokes action result and on this signal service view model result is stored in HttpContext.Items dictionary with uniques key - for example service name as it's uniques. When request for given service completes, request handler for given service will extract view models from HttpContext.Items dictionary and returns view models to main application.
Example of storing view models, publishing request and returning view models:

 public class ViewModelResult : ActionResult
    {
        public override void ExecuteResult(ControllerContext context)
        {
            foreach (var viewModel in ViewModels)
            {
                var cast = viewModel as ViewModel;
                if (cast != null)
                    cast.UpdateViewModel(context);
            }

            var key = (string) context.RouteData.DataTokens[Consts.RouteServiceKey];
            var list = context.HttpContext.Items[key] as List<IViewModel> ?? new List<IViewModel>();
            list.AddRange(ViewModels);
            context.HttpContext.Items[key] = list;
        }
    }


public class RequestHandler
{
        private async Task<IEnumerable<IViewModel>> GenerateResponse(RouteValueDictionary values)
        {
            if (Routes == null || !Routes.Any())
                throw new NotImplementedException("Routes");
            var viewModelsResultKey = (string)null;
            var list = new List<IViewModel>();
            var context = HttpContext.Current;
            var wrap = new HttpContextWrapper(context);
            var handler = GetHandler(wrap, values, out viewModelsResultKey);
            if (handler != null)
            {
                try
                {
                    HttpContext.Current.Items[viewModelsResultKey] = new List<IViewModel>();
                    await Task.Factory.FromAsync(handler.BeginProcessRequest, handler.EndProcessRequest, context, null);
                    list = (List<IViewModel>)HttpContext.Current.Items[viewModelsResultKey] ?? new List<IViewModel>();
                }
                catch
                {
                }
                finally
                {
                    HttpContext.Current.Items.Remove(viewModelsResultKey);
                }
                var ex = list.FirstOrDefault(p => p is ExceptionViewModel);
                if (ex != null)
                {
                    var cast = ex as ExceptionViewModel;
                    ExceptionDispatchInfo.Capture(cast.Exception).Throw();
                }
            }
            return list;
        }
}

Because when MvcHandler cannot find invoke handler for request, or something goes wrong, it throws exception, so we catch this exception. Because we want to pass any exception that was thrown in service action, we add excetion handler in all service controllers, so when exception is throw we catch, pack it into appropriate exception view model and completes request. After MvcHandler finishes, we unpack returned view models and if any of them is exception view model we rethrows contained exception as if it was thrown in direct call. In this way we can preserve stack trace and other informations. If all view models were created successfully, we simply return them to main application.

Clone this wiki locally