Very often we mess up with routing by giving path which does not exist due to various reason like spelling mistake, name confusions etc. for forming correct URLs . This happens on both server side codes and client side code. There are few strong typed routing provider library present for server side but none is present on the client side.
We will look into creating a classes and function to set up our routing paths which will help us to give correct URL based on MVC routing.
The idea is to create T4 template file which will be generating client side codes based on MVC routing.
I have tried to keep it simple in this version which reads up MVC controllers and public function.
The end result looks like this, which is TypeScript code. Makes it pretty simple to use these classes directly without worrying about correct URLs.
The DomainName variable can be initialized out of the class to get full path for URLs.
Ex:
Let's look into T4 template, which generates above code.
Mark that avoidClassNames and avoidFunctionNames is used to avoid classes and function which we want to avoid in generation of codes. In my case, class named with BaseController.
There are few things which are not supported in this version:
1. Attribute routing of MVC.
2. WebApi routing.
Note: GetAllCodeElementsOfType is taken from https://github.com/PombeirP/T4Factories/blob/master/T4Factories.Testbed/CodeTemplates/VisualStudioAutomationHelper.ttinclude
.
We will look into creating a classes and function to set up our routing paths which will help us to give correct URL based on MVC routing.
The idea is to create T4 template file which will be generating client side codes based on MVC routing.
I have tried to keep it simple in this version which reads up MVC controllers and public function.
The end result looks like this, which is TypeScript code. Makes it pretty simple to use these classes directly without worrying about correct URLs.
module Routing {
export declare var DomainName: string;
export function GetDomainName(): string {
return (DomainName != undefined && DomainName.length) ?
(DomainName.lastIndexOf("/") == -1 ? DomainName + "/" : DomainName)
: "";
}
// Routing for Web.Controllers.TestController
export class TestRoute {
static TestConfiguration() : string
{
return GetDomainName() + "Test/" + "QuestionConfiguration";
}
static GetAnswerTypes() : string
{
return GetDomainName() + "Test/" + "GetAnswerTypes";
}
static GetCascadeValues() : string
{
return GetDomainName() + "Test/" + "GetCascadeValues";
}
}
}
The DomainName variable can be initialized out of the class to get full path for URLs.
Ex:
Routing.DomainName = '@MvcApplication.WebsiteDomainName';
Let's look into T4 template, which generates above code.
<#@ template debug="true" hostSpecific="true" #>
<#@ assembly name="System.Core" #>
<#@ Assembly Name="EnvDTE" #>
<#@ import namespace="EnvDTE" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".ts" #>
module Routing {
export declare var DomainName: string;
export function GetDomainName(): string {
return (DomainName != undefined && DomainName.length) ?
(DomainName.lastIndexOf("/") == -1 ? DomainName + "/" : DomainName)
: "";
}
<#
// TODO: Give any controllers which need to be avoided.
var avoidClassNames = new List<string>{"BaseController"};
// TODO: Function names to avoid in code generation.
var avoidFunctionNames = new List<string>();
var env = (DTE)((IServiceProvider)this.Host)
.GetService(typeof(EnvDTE.DTE));
var project = env.ActiveDocument.ProjectItem.ContainingProject;
foreach(CodeClass clas in
GetAllCodeElementsOfType(project.CodeModel.CodeElements,EnvDTE.vsCMElement.vsCMElementClass,false)
.OfType<CodeClass>()
.Where(cc => cc.IsDerivedFrom["System.Web.Mvc.Controller"] &&
!avoidClassNames.Contains(cc.Name)
)
)
{
var clasName = clas.Name.Substring(0, clas.Name.Length-10);
#>
// Routing for <#= clas.FullName #>
export class <#= clasName #>Route {
<#
var allFunctions = GetAllCodeElementsOfType(clas.Members, EnvDTE.vsCMElement.vsCMElementFunction, false);
foreach(var funcName in allFunctions.OfType<CodeFunction>()
.Where(fun => fun.Access == EnvDTE.vsCMAccess.vsCMAccessPublic &&
fun.Name != clas.Name &&
!avoidFunctionNames.Contains(fun.Name)
)
.Select(fun=>fun.Name).Distinct()
)
{
#>
static <#=funcName #>() : string
{
return GetDomainName() + "<#=clasName #>/" + "<#=funcName #>";
}
<#
}
#>
}
<#
}
#>
}
<#+
// https://github.com/PombeirP/T4Factories/blob/master/T4Factories.Testbed/CodeTemplates/VisualStudioAutomationHelper.ttinclude
// This function is from Tangible T4. All credit goes to them
public List<EnvDTE.CodeElement> GetAllCodeElementsOfType(EnvDTE.CodeElements elements, EnvDTE.vsCMElement elementType, bool includeExternalTypes)
{
var ret = new List<EnvDTE.CodeElement>();
foreach (EnvDTE.CodeElement elem in elements)
{
// iterate all namespaces (even if they are external)
// > they might contain project code
if (elem.Kind == EnvDTE.vsCMElement.vsCMElementNamespace)
{
ret.AddRange(GetAllCodeElementsOfType(((EnvDTE.CodeNamespace)elem).Members, elementType, includeExternalTypes));
}
// if its not a namespace but external
// > ignore it
else if (elem.InfoLocation == EnvDTE.vsCMInfoLocation.vsCMInfoLocationExternal
&& !includeExternalTypes)
continue;
// if its from the project
// > check its members
else if (elem.IsCodeType)
{
ret.AddRange(GetAllCodeElementsOfType(((EnvDTE.CodeType)elem).Members, elementType, includeExternalTypes));
}
// if this item is of the desired type
// > store it
if (elem.Kind == elementType)
ret.Add(elem);
}
return ret;
}
#>
Mark that avoidClassNames and avoidFunctionNames is used to avoid classes and function which we want to avoid in generation of codes. In my case, class named with BaseController.
There are few things which are not supported in this version:
1. Attribute routing of MVC.
2. WebApi routing.
Note: GetAllCodeElementsOfType is taken from https://github.com/PombeirP/T4Factories/blob/master/T4Factories.Testbed/CodeTemplates/VisualStudioAutomationHelper.ttinclude
.
Comments
Post a Comment