forked from reactiveui/ReactiveUI
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathViewLocator.cs
More file actions
executable file
·215 lines (185 loc) · 8.03 KB
/
ViewLocator.cs
File metadata and controls
executable file
·215 lines (185 loc) · 8.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MS-PL license.
// See the LICENSE file in the project root for more information.
using System;
using System.Reflection;
using ReactiveUI;
using Splat;
namespace ReactiveUI
{
public static class ViewLocator
{
public static IViewLocator Current
{
get
{
var ret = Locator.Current.GetService<IViewLocator>();
if (ret == null) {
throw new Exception("Could not find a default ViewLocator. This should never happen, your dependency resolver is broken");
}
return ret;
}
}
}
internal sealed class DefaultViewLocator : IViewLocator, IEnableLogger
{
public DefaultViewLocator(Func<string, string> viewModelToViewFunc = null)
{
ViewModelToViewFunc = viewModelToViewFunc ?? (vm => vm.Replace("ViewModel", "View"));
}
/// <summary>
/// Gets or sets a function that is used to convert a view model name to a proposed view name.
/// </summary>
/// <remarks>
/// <para>
/// If unset, the default behavior is to change "ViewModel" to "View". If a different convention is followed, assign an appropriate function to this
/// property.
/// </para>
/// <para>
/// Note that the name returned by the function is a starting point for view resolution. Variants on the name will be resolved according to the rules
/// set out by the <see cref="ResolveView"/> method.
/// </para>
/// </remarks>
public Func<string, string> ViewModelToViewFunc { get; set; }
/// <summary>
/// Returns the view associated with a view model, deriving the name of the type via <see cref="ViewModelToViewFunc"/>, then discovering it via the
/// service locator.
/// </summary>
/// <remarks>
/// <para>
/// Given view model type <c>T</c> with runtime type <c>RT</c>, this implementation will attempt to resolve the following views:
/// <list type="number">
/// <item>
/// <description>
/// Look for a service registered under the type whose name is given to us by passing <c>RT</c> to <see cref="ViewModelToViewFunc"/> (which defaults to changing "ViewModel" to "View").
/// </description>
/// </item>
/// <item>
/// <description>
/// Look for a service registered under the type <c>IViewFor<RT></c>.
/// </description>
/// </item>
/// <item>
/// <description>
/// Look for a service registered under the type whose name is given to us by passing <c>T</c> to <see cref="ViewModelToViewFunc"/> (which defaults to changing "ViewModel" to "View").
/// </description>
/// </item>
/// <item>
/// <description>
/// Look for a service registered under the type <c>IViewFor<T></c>.
/// </description>
/// </item>
/// <item>
/// <description>
/// If <c>T</c> is an interface, change its name to that of a class (i.e. drop the leading "I"). If it's a class, change to an interface (i.e. add a leading "I").
/// </description>
/// </item>
/// <item>
/// <description>
/// Repeat steps 1-4 with the type resolved from the modified name.
/// </description>
/// </item>
/// </list>
/// </para>
/// </remarks>
/// <param name="viewModel">
/// The view model whose associated view is to be resolved.
/// </param>
/// <param name="contract">
/// Optional contract to be used when resolving from Splat
/// </param>
/// <returns>
/// The view associated with the given view model.
/// </returns>
public IViewFor ResolveView<T>(T viewModel, string contract = null)
where T : class
{
var view = this.AttemptViewResolutionFor(viewModel.GetType(), contract);
if (view != null) {
return view;
}
view = this.AttemptViewResolutionFor(typeof(T), contract);
if (view != null) {
return view;
}
view = this.AttemptViewResolutionFor(ToggleViewModelType(viewModel.GetType()), contract);
if (view != null) {
return view;
}
view = this.AttemptViewResolutionFor(ToggleViewModelType(typeof(T)), contract);
if (view != null) {
return view;
}
this.Log().Warn("Failed to resolve view for view model type '{0}'.", typeof(T).FullName);
return null;
}
private IViewFor AttemptViewResolutionFor(Type viewModelType, string contract)
{
if (viewModelType == null) return null;
var viewModelTypeName = viewModelType.AssemblyQualifiedName;
var proposedViewTypeName = this.ViewModelToViewFunc(viewModelTypeName);
var view = this.AttemptViewResolution(proposedViewTypeName, contract);
if (view != null) {
return view;
}
proposedViewTypeName = typeof(IViewFor<>).MakeGenericType(viewModelType).AssemblyQualifiedName;
view = this.AttemptViewResolution(proposedViewTypeName, contract);
if (view != null) {
return view;
}
return null;
}
private IViewFor AttemptViewResolution(string viewTypeName, string contract)
{
try {
var viewType = Reflection.ReallyFindType(viewTypeName, throwOnFailure: false);
if (viewType == null) {
this.Log().Debug("Failed to find type named '{0}'.", viewTypeName);
return null;
}
var service = Locator.Current.GetService(viewType, contract);
if (service == null) {
this.Log().Debug("Failed to resolve service for type '{0}'.", viewType.FullName);
return null;
}
var view = service as IViewFor;
if (view == null) {
this.Log().Debug("Resolve service type '{0}' does not implement '{1}'.", viewType.FullName, typeof(IViewFor).FullName);
return null;
}
return view;
} catch (Exception ex) {
this.Log().ErrorException("Exception occurred whilst attempting to resolve type '" + viewTypeName + "' into a view.", ex);
throw;
}
}
private static Type ToggleViewModelType(Type viewModelType)
{
var viewModelTypeName = viewModelType.AssemblyQualifiedName;
if (viewModelType.GetTypeInfo().IsInterface) {
if (viewModelType.Name.StartsWith("I")) {
var toggledTypeName = DeinterfaceifyTypeName(viewModelTypeName);
var toggledType = Reflection.ReallyFindType(toggledTypeName, throwOnFailure: false);
return toggledType;
}
} else {
var toggledTypeName = InterfaceifyTypeName(viewModelTypeName);
var toggledType = Reflection.ReallyFindType(toggledTypeName, throwOnFailure: false);
return toggledType;
}
return null;
}
private static string DeinterfaceifyTypeName(string typeName)
{
var idxComma = typeName.IndexOf(',');
var idxPeriod = typeName.LastIndexOf('.', idxComma - 1);
return typeName.Substring(0, idxPeriod + 1) + typeName.Substring(idxPeriod + 2);
}
private static string InterfaceifyTypeName(string typeName)
{
var idxComma = typeName.IndexOf(',');
var idxPeriod = typeName.LastIndexOf('.', idxComma - 1);
return typeName.Insert(idxPeriod + 1, "I");
}
}
}