/src/Nancy/ViewEngines/DefaultViewFactory.cs

https://github.com/csainty/Nancy · C# · 178 lines · 130 code · 30 blank · 18 comment · 15 complexity · c2bea25ab1482c6ccfaac6310e333c4e MD5 · raw file

  1. namespace Nancy.ViewEngines
  2. {
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Dynamic;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Text.RegularExpressions;
  9. using Nancy.Conventions;
  10. /// <summary>
  11. /// The default implementation for how views are resolved and rendered by Nancy.
  12. /// </summary>
  13. public class DefaultViewFactory : IViewFactory
  14. {
  15. private readonly IViewResolver viewResolver;
  16. private readonly IEnumerable<IViewEngine> viewEngines;
  17. private readonly IRenderContextFactory renderContextFactory;
  18. private readonly ViewLocationConventions conventions;
  19. private readonly IRootPathProvider rootPathProvider;
  20. private static readonly Action<Stream> EmptyView = x => { };
  21. private readonly string[] viewEngineExtensions;
  22. /// <summary>
  23. /// Initializes a new instance of the <see cref="DefaultViewFactory"/> class.
  24. /// </summary>
  25. /// <param name="viewResolver">An <see cref="IViewResolver"/> instance that should be used to resolve the location of a view.</param>
  26. /// <param name="viewEngines">An <see cref="IEnumerable{T}"/> instance containing the <see cref="IViewEngine"/> instances that should be able to be used to render a view</param>
  27. /// <param name="renderContextFactory">A <see cref="IRenderContextFactory"/> instance that should be used to create an <see cref="IRenderContext"/> when a view is rendered.</param>
  28. /// <param name="conventions">An <see cref="ViewLocationConventions"/> instance that should be used to resolve all possible view locations </param>
  29. /// <param name="rootPathProvider">An <see cref="IRootPathProvider"/> instance.</param>
  30. public DefaultViewFactory(IViewResolver viewResolver, IEnumerable<IViewEngine> viewEngines, IRenderContextFactory renderContextFactory, ViewLocationConventions conventions, IRootPathProvider rootPathProvider)
  31. {
  32. this.viewResolver = viewResolver;
  33. this.viewEngines = viewEngines;
  34. this.renderContextFactory = renderContextFactory;
  35. this.conventions = conventions;
  36. this.rootPathProvider = rootPathProvider;
  37. this.viewEngineExtensions = this.viewEngines.SelectMany(ive => ive.Extensions).ToArray();
  38. }
  39. /// <summary>
  40. /// Renders the view with the name and model defined by the <paramref name="viewName"/> and <paramref name="model"/> parameters.
  41. /// </summary>
  42. /// <param name="viewName">The name of the view to render.</param>
  43. /// <param name="model">The model that should be passed into the view.</param>
  44. /// <param name="viewLocationContext">A <see cref="ViewLocationContext"/> instance, containing information about the context for which the view is being rendered.</param>
  45. /// <returns>A delegate that can be invoked with the <see cref="Stream"/> that the view should be rendered to.</returns>
  46. public Response RenderView(string viewName, dynamic model, ViewLocationContext viewLocationContext)
  47. {
  48. if (viewName == null && model == null)
  49. {
  50. throw new ArgumentException("View name and model parameters cannot both be null.");
  51. }
  52. if (model == null && viewName.Length == 0)
  53. {
  54. throw new ArgumentException("The view name parameter cannot be empty when the model parameters is null.");
  55. }
  56. if (viewLocationContext == null)
  57. {
  58. throw new ArgumentNullException("viewLocationContext", "The value of the viewLocationContext parameter cannot be null.");
  59. }
  60. var actualViewName =
  61. viewName ?? GetViewNameFromModel(model, viewLocationContext.Context);
  62. viewLocationContext.Context.Trace.TraceLog.WriteLog(x => x.AppendLine(string.Concat("[DefaultViewFactory] Rendering view with name ", actualViewName)));
  63. return this.GetRenderedView(actualViewName, model, viewLocationContext);
  64. }
  65. private Response GetRenderedView(string viewName, dynamic model, ViewLocationContext viewLocationContext)
  66. {
  67. var viewLocationResult =
  68. this.viewResolver.GetViewLocation(viewName, model, viewLocationContext);
  69. var resolvedViewEngine =
  70. GetViewEngine(viewLocationResult, viewLocationContext.Context);
  71. if (resolvedViewEngine == null)
  72. {
  73. viewLocationContext.Context.Trace.TraceLog.WriteLog(x => x.AppendLine("[DefaultViewFactory] Unable to find view engine that could render the view."));
  74. throw new ViewNotFoundException(viewName, this.viewEngineExtensions, this.GetInspectedLocations(viewName, model, viewLocationContext), this.rootPathProvider);
  75. }
  76. viewLocationContext.Context.Trace.TraceLog.WriteLog(x => x.AppendLine(string.Concat("[DefaultViewFactory] Rendering view with view engine ", resolvedViewEngine.GetType().FullName)));
  77. return SafeInvokeViewEngine(
  78. resolvedViewEngine,
  79. viewLocationResult,
  80. GetSafeModel(model),
  81. this.renderContextFactory.GetRenderContext(viewLocationContext)
  82. );
  83. }
  84. private string[] GetInspectedLocations(string viewName, dynamic model, ViewLocationContext viewLocationContext)
  85. {
  86. var inspectedLocations = new List<string>();
  87. foreach (var convention in conventions)
  88. {
  89. try
  90. {
  91. var location =
  92. convention.Invoke(viewName, model, viewLocationContext);
  93. if (!string.IsNullOrWhiteSpace(location))
  94. {
  95. inspectedLocations.Add(location);
  96. }
  97. }
  98. catch
  99. {
  100. }
  101. }
  102. return inspectedLocations.ToArray();
  103. }
  104. private static object GetSafeModel(object model)
  105. {
  106. return (model.IsAnonymousType()) ? GetExpandoObject(model) : model;
  107. }
  108. private static ExpandoObject GetExpandoObject(object source)
  109. {
  110. var expandoObject = new ExpandoObject();
  111. IDictionary<string, object> results = expandoObject;
  112. foreach (var propertyInfo in source.GetType().GetProperties())
  113. {
  114. results[propertyInfo.Name] = propertyInfo.GetValue(source, null);
  115. }
  116. return expandoObject;
  117. }
  118. private IViewEngine GetViewEngine(ViewLocationResult viewLocationResult, NancyContext context)
  119. {
  120. if (viewLocationResult == null)
  121. {
  122. return null;
  123. }
  124. context.Trace.TraceLog.WriteLog(x => x.AppendLine(string.Concat("[DefaultViewFactory] Attempting to resolve view engine for view extension ", viewLocationResult.Extension)));
  125. var matchingViewEngines =
  126. from viewEngine in this.viewEngines
  127. where viewEngine.Extensions.Any(x => x.Equals(viewLocationResult.Extension, StringComparison.InvariantCultureIgnoreCase))
  128. select viewEngine;
  129. return matchingViewEngines.FirstOrDefault();
  130. }
  131. private static string GetViewNameFromModel(dynamic model, NancyContext context)
  132. {
  133. context.Trace.TraceLog.WriteLog(x => x.AppendLine(string.Concat("[DefaultViewFactory] Extracting view name from model of type ", model.GetType().FullName)));
  134. return Regex.Replace(model.GetType().Name, "Model$", string.Empty);
  135. }
  136. private static Response SafeInvokeViewEngine(IViewEngine viewEngine, ViewLocationResult locationResult, dynamic model, IRenderContext renderContext)
  137. {
  138. try
  139. {
  140. return viewEngine.RenderView(locationResult, model, renderContext);
  141. }
  142. catch (Exception)
  143. {
  144. return EmptyView;
  145. }
  146. }
  147. }
  148. }