-
Notifications
You must be signed in to change notification settings - Fork 3
Delivering view models
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.
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.
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
}
}
}
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.
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.
-
Welcome
1.1 Project overview
1.2 Features -
Introduction
2.1 UI composition example
2.2 Service separation
2.3 Service communication
2.4 UI composition goals
2.5 Clues how to start
2.6 Potencial problems -
Identifying widgets
3.1 Naming widgets
3.2 Widgets format
3.3 Amount of widgets
3.4 Widgets and service boundaries
3.5 Widgets and caching
3.6 Grid for widgets -
Delivering view models
4.1 Publishing request
4.2 Internal routing
4.3 Data in view models
4.4 Gathering view models -
Rendering view models
5.1 External routing
5.2 Including route values
5.3 Finiding physical view files
5.4 Template views - Getting data from request
- Sharing resources
- Service api
-
Transactions
9.1 One transaction
9.2 Multiple transactions
9.3 Transaction performance - Dependency injection
- Public api changes
- [Tables] (https://github.com/padzikm/CompositeUI/wiki/Tables)
12.1 Table order
12.2 Server-side rendering
12.3 Client-side rendering
12.4 Nested tables -
Cross-service validation
13.1 Server-side validation
13.2 Client-side validation -
CRUD
14.1 Service private data
14.2 Create
14.3 Update
14.4 Delete
14.5 Preview -
Caching
15.1 View models
15.2 Widgets
15.3 Pages - Optimizing network calls
- Scalling
- Client-side communication
- Deployment
- Starting new project