-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathServiceCollectionExtensions.cs
271 lines (252 loc) · 16 KB
/
ServiceCollectionExtensions.cs
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
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
/*==============================================================================================================================
| Author Ignia, LLC
| Client Ignia, LLC
| Project Topics Library
\=============================================================================================================================*/
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using OnTopic.AspNetCore.Mvc.Controllers;
namespace OnTopic.AspNetCore.Mvc {
/*============================================================================================================================
| CLASS: SERVICE COLLECTION EXTENSIONS
\---------------------------------------------------------------------------------------------------------------------------*/
/// <summary>
/// Provides extension methods for configuring the OnTopic library with ASP.NET Core MVC.
/// </summary>
public static class ServiceCollectionExtensions {
/*==========================================================================================================================
| EXTENSION: ADD TOPIC SUPPORT (IMVCBUILDER)
\-------------------------------------------------------------------------------------------------------------------------*/
/// <summary>
/// Configures the Razor engine to include OnTopic view locations via the <see cref="TopicViewLocationExpander"/>.
/// </summary>
public static IMvcBuilder AddTopicSupport(this IMvcBuilder services) {
/*------------------------------------------------------------------------------------------------------------------------
| Validate parameters
\-----------------------------------------------------------------------------------------------------------------------*/
Contract.Requires(services, nameof(services));
/*------------------------------------------------------------------------------------------------------------------------
| Register services
\-----------------------------------------------------------------------------------------------------------------------*/
services.Services.TryAddSingleton<IActionResultExecutor<TopicViewResult>, TopicViewResultExecutor>();
services.Services.TryAddSingleton<TopicRouteValueTransformer>();
/*------------------------------------------------------------------------------------------------------------------------
| Configure services
\-----------------------------------------------------------------------------------------------------------------------*/
services.AddRazorOptions(o => {
o.ViewLocationExpanders.Add(new TopicViewLocationExpander());
});
/*------------------------------------------------------------------------------------------------------------------------
| Register local controllers
\-----------------------------------------------------------------------------------------------------------------------*/
//Add Topic assembly into scope
services.AddApplicationPart(typeof(TopicController).Assembly);
/*------------------------------------------------------------------------------------------------------------------------
| Return services for fluent API
\-----------------------------------------------------------------------------------------------------------------------*/
return services;
}
/*==========================================================================================================================
| EXTENSION: MAP TOPIC ROUTE (IROUTEBUILDER)
\-------------------------------------------------------------------------------------------------------------------------*/
/// <summary>
/// Adds an MVC route for handling OnTopic related requests, and maps it to the <see cref="TopicController"/> by default.
/// </summary>
/// <remarks>
/// For ASP.NET Core 3, prefer instead <see cref="MapTopicRoute(IEndpointRouteBuilder, String, String, String)"/>, as
/// endpoint routing is preferred in ASP.NET Core 3. OnTopic also offers far more extension methods for endpoint routing,
/// while this method is provided exclusively for backward compatibility.
/// </remarks>
[ExcludeFromCodeCoverage]
[Obsolete("This method is deprecated and will be removed in OnTopic 5. Callers should migrate to endpoint routing.", true)]
public static IRouteBuilder MapTopicRoute(
this IRouteBuilder routes,
string rootTopic,
string controller = "Topic",
string action = "Index"
) =>
routes.MapRoute(
name: $"{rootTopic}Topic",
template: rootTopic + "/{*path}",
defaults: new { controller, action, rootTopic }
);
/*==========================================================================================================================
| EXTENSION: MAP TOPIC ROUTE (IENDPOINTROUTEBUILDER)
\-------------------------------------------------------------------------------------------------------------------------*/
/// <summary>
/// Adds the <c>{rootTopic}/{**path}</c> endpoint route for a specific root topic, which enables that root to be mapped to
/// specific topics via the <see cref="TopicRepositoryExtensions.Load(Repositories.ITopicRepository, RouteData)"/>
/// extension method used by <see cref="TopicController"/>.
/// </summary>
public static ControllerActionEndpointConventionBuilder MapTopicRoute(
this IEndpointRouteBuilder routes,
string rootTopic,
string controller = "Topic",
string action = "Index"
) =>
routes.MapControllerRoute(
name: $"{rootTopic}Topic",
pattern: rootTopic + "/{**path}",
defaults: new { controller, action, rootTopic }
);
/*==========================================================================================================================
| EXTENSION: MAP TOPIC AREA ROUTE (IENDPOINTROUTEBUILDER)
\-------------------------------------------------------------------------------------------------------------------------*/
/// <summary>
/// Adds the <c>{areaName}/{**path}</c> endpoint route for a specific area, which enables the areas to be mapped to
/// specific topics via the <see cref="TopicRepositoryExtensions.Load(Repositories.ITopicRepository, RouteData)"/>
/// extension method used by <see cref="TopicController"/>.
/// </summary>
/// <remarks>
/// If there are multiple routes that fit this description, you can instead opt to use the <see cref=
/// "MapTopicAreaRoute(IEndpointRouteBuilder)"/> extension, which will register all areas.
/// </remarks>
[ExcludeFromCodeCoverage]
public static ControllerActionEndpointConventionBuilder MapTopicAreaRoute(
this IEndpointRouteBuilder routes,
string areaName,
string? controller = null,
string action = "Index"
) =>
routes.MapAreaControllerRoute(
name: $"TopicAreas",
areaName: areaName,
pattern: areaName + "/{**path}",
defaults: new { controller = controller?? areaName, action, rootTopic = areaName }
);
/// <summary>
/// Adds the <c>{area:exists}/{**path}</c> endpoint route for all areas, which enables the areas to be mapped to specific
/// topics via the <see cref="TopicRepositoryExtensions.Load(Repositories.ITopicRepository, RouteData)"/> extension method
/// used by <see cref="TopicController"/>.
/// </summary>
/// <remarks>
/// Be aware that this method uses the <see cref="ControllerEndpointRouteBuilderExtensions.MapDynamicControllerRoute{
/// TTransformer}(IEndpointRouteBuilder, String)"/> method. In .NET 3.x, this is incompatible with both the <see cref=
/// "AnchorTagHelper"/> and <see cref="LinkGenerator"/> classes. This means that e.g. <c>@Url.Action()</c> references
/// in views won't be properly formed. If these are required, prefer registering each route individually using <see cref=
/// "MapTopicAreaRoute(IEndpointRouteBuilder, String, String?, String)"/>.
/// </remarks>
public static void MapTopicAreaRoute(this IEndpointRouteBuilder routes) =>
routes.MapDynamicControllerRoute<TopicRouteValueTransformer>("{area:exists}/{**path}");
/*==========================================================================================================================
| EXTENSION: MAP DEFAULT AREA CONTROLLER ROUTES (IENDPOINTROUTEBUILDER)
\-------------------------------------------------------------------------------------------------------------------------*/
/// <summary>
/// Adds the fully-qualified <c>{area:exists}/{controller}/{action=Index}/{id?}</c> endpoint route for all areas.
/// </summary>
/// <remarks>
/// This is analogous to the standard <see cref="ControllerEndpointRouteBuilderExtensions.MapDefaultControllerRoute(
/// IEndpointRouteBuilder)"/> method that ships with ASP.NET, except that it works with areas.
/// </remarks>
public static void MapDefaultAreaControllerRoute(this IEndpointRouteBuilder routes) =>
routes.MapControllerRoute(
name: "TopicAreas",
pattern: "{area:exists}/{controller}/{action=Index}/{id?}"
);
/*==========================================================================================================================
| EXTENSION: MAP IMPLICIT AREA CONTROLLER ROUTES (IENDPOINTROUTEBUILDER)
\-------------------------------------------------------------------------------------------------------------------------*/
/// <summary>
/// Adds the <c>{areaName}/{action=Index}</c> endpoint route for a specific area where the controller has the same name as
/// the area.
/// </summary>
/// <remarks>
/// <para>
/// This extension method implicitly assigns the controller name based on the area name. This is advantageous when an
/// area has a single controller which is named after the area—e.g., <c>[Area("Forms")]</c> and <c>FormsController</c>—
/// as this allows the redundant <c>Controller</c> to be ommited from the route (e.g., <c>/Forms/Forms/{action}</c>.
/// </para>
/// <para>
/// If there are multiple routes that fit this description, you can instead opt to use the <see cref=
/// "MapImplicitAreaControllerRoute(IEndpointRouteBuilder)"/> overload, which will register all areas.
/// </para>
/// </remarks>
[ExcludeFromCodeCoverage]
public static void MapImplicitAreaControllerRoute(this IEndpointRouteBuilder routes, string areaName) =>
routes.MapAreaControllerRoute(
name: $"{areaName}TopicArea",
areaName: areaName,
pattern: $"{areaName}/{{action}}",
defaults: new { controller = areaName }
);
/// <summary>
/// Adds the <c>{area:exists}/{action=Index}</c> endpoint route for all areas where the controller has the same name as
/// the area.
/// </summary>
/// <remarks>
/// <para>
/// This extension method implicitly assigns the controller name based on the area name. This is advantageous when there
/// are multiple areas which have a single controller which is named after the area—e.g., <c>[Area("Forms")]</c> and
/// <c>FormsController: Controller</c>—as this allows those to be collectively registered under a single route, without
/// needing the redundant <c>Controller</c> value to be defined in the route (e.g., <c>/Forms/Forms/{action}</c>.
/// </para>
/// <para>
/// Be aware that this method uses the <see cref="ControllerEndpointRouteBuilderExtensions.MapDynamicControllerRoute{
/// TTransformer}(IEndpointRouteBuilder, String)"/> method. In .NET 3.x, this is incompatible with both the <see cref=
/// "AnchorTagHelper"/> and <see cref="LinkGenerator"/> classes. This means that e.g. <c>@Url.Action()</c> references
/// in views won't be properly formed. If these are required, prefer registering each route individually using <see
/// cref="MapImplicitAreaControllerRoute(IEndpointRouteBuilder, String)"/>.
/// </para>
/// </remarks>
public static void MapImplicitAreaControllerRoute(this IEndpointRouteBuilder routes) =>
routes.MapDynamicControllerRoute<TopicRouteValueTransformer>("{area:exists}/{action=Index}");
/*==========================================================================================================================
| EXTENSION: MAP ERROR ROUTE (IENDPOINTROUTEBUILDER)
\-------------------------------------------------------------------------------------------------------------------------*/
/// <summary>
/// Adds the <c>/Error/{errorCode}</c> endpoint route for the <see cref="ErrorController"/>.
/// </summary>
/// <remarks>
/// This allows the <see cref="ErrorController"/> to be used in conjunction with e.g., the <see cref="
/// StatusCodePagesExtensions.UseStatusCodePages(IApplicationBuilder)"/>, by providing a route for capturing the <c>
/// errorCode</c>.
/// </remarks>
/// <param name="routes">The <see cref="IEndpointRouteBuilder"/> this route is being added to.</param>
/// <param name="rootTopic">The name of the root topic that the route should be mapped to. Defaults to <c>Error</c>.</param>
/// <param name="includeStaticFiles">Determines if static resources should be covered. Defaults to <c>true</c>.</param>
public static ControllerActionEndpointConventionBuilder MapTopicErrors(
this IEndpointRouteBuilder routes,
string rootTopic = "Error",
bool includeStaticFiles = true
) =>
routes.MapControllerRoute(
name: "TopicError",
pattern: $"{rootTopic}/{{id:int}}/",
defaults: new { controller = "Error", action = "Http", rootTopic, includeStaticFiles }
);
/*==========================================================================================================================
| EXTENSION: MAP TOPIC SITEMAP (IENDPOINTROUTEBUILDER)
\-------------------------------------------------------------------------------------------------------------------------*/
/// <summary>
/// Adds the <c>Sitemap/{action=Index}</c> endpoint route for the OnTopic sitemap.
/// </summary>
/// <remarks>
/// For most implementations, this will be covered by the default route, such as that implemented by the standard <see
/// cref="ControllerEndpointRouteBuilderExtensions.MapDefaultControllerRoute(IEndpointRouteBuilder)"/> method that ships
/// with ASP.NET. This extension method is provided as a convenience method for implementations that aren't using the
/// standard route, for whatever reason, and want a specific route setup for the sitemap.
/// </remarks>
public static ControllerActionEndpointConventionBuilder MapTopicSitemap(this IEndpointRouteBuilder routes) =>
routes.MapControllerRoute(
name: "TopicSitemap",
pattern: "Sitemap/{action=Index}",
defaults: new { controller = "Sitemap" }
);
/*==========================================================================================================================
| EXTENSION: MAP TOPIC REDIRECT (IENDPOINTROUTEBUILDER)
\-------------------------------------------------------------------------------------------------------------------------*/
/// <summary>
/// Adds an MVC route for handling OnTopic redirect requests, and maps it to the <see cref="RedirectController"/>.
/// </summary>
public static ControllerActionEndpointConventionBuilder MapTopicRedirect(this IEndpointRouteBuilder routes) =>
routes.MapControllerRoute(
name: "TopicRedirect",
pattern: "Topic/{topicId}",
defaults: new { controller = "Redirect", action = "Redirect" }
);
} //Class
} //Namespace