.NET, C#, TextTemplate, visual studio

Run Custom Tool on Build for Visual Studio 2015

Background

The project that I’m working on supports multiple platform and the approach we’re doing is that we’re sharing codes across solutions. It means that some project/solution adds the source files as link from a common location. Resource (*.resx) and TextTemplate (*.tt) files are being shared across multiple projects and possibly in different platform such as .NET 2.0, .NET 4.5 and UWP.

Problem

Every time we change some part of the code in one project it might affect the output of the custom tool generated files and the custom tool (ResxFileCodeGenerator) for resource files doesn’t give exactly the same output in every platform. Now we have to run the custom tool on every file whenever we switch from one solution to another.

RunCustomToolOnBuild.vsix

RunCustomtoolOnBuild is a visual studio extension that tries to run the custom tools associated to project files on build. You can download it from visual studio gallery or get the source in GitHub.

How to use:
1. Install the RunCustomToolOnBuild.vsix
2. Open Visual Studio 2015
3. Right click on the resource or text template file then click properties.
4. In the properties set the RunCustomToolOnBuild to True.
5. Build the solution. Notice that the resx or TT file regenerates the output files.

RunCustomToolOnBuild

 

Credits to Thomas Levesque for his AutoRunCustomTool in Github.

 

Advertisements
.NET, C#, MVVM, TextTemplate, XAML

Using TextTemplate to Generate Wrapper classes

Wrapping classes is sometimes necessary in development. The MVVM pattern usually wraps the model inside a viewmodel class for it tobe bindable. In brokered component we can’t expose a class from a referenced library unless we wrap it in a sealed class. The problem with wrapping is that we repeat the same code over and we expose properties and function with the same name and we have to retype it and it takes some amount of time.

The good news is that there’s an item in visual studio called TextTemplate. Text template is a scriptable file that can generate code for you and those generated files are usable within the assembly.

Now going back to the problem of wrapping classes. We as developers doesn’t want to spend time typing repetitive codes. Using reflection I managed to create a reusable script that can generate a wrapper class. This script will generate wrapped properties, methods, constructors and nested class. You can download the TextTemplate source code from my Github repo.

Basic Class Wrapping

Classes that will be wrapped within a wrapper:

public class User
{
public string FirstName { get; set; }

public string LastName { get; set; }

public string UserName { get; set; }

public int Role { get; set; }

public int InvalidLoginAttempt { get; set; }

}

public class Driver
{
public Driver(string name) { }

public Driver() { Nested = new SomeNestedClass(); }

public string DriverId { get; private set; }

public string DriverName { get; set; }

public string TruckNo { get; set; }

public SomeNestedClass Nested { get; set; }

public User UserInfo { get; set; }

public void StartShift(string p1, string p2, ref int odometer, params object[] args) { Console.WriteLine("StartShift"); }

public int EndShift(string p1, string p2, out int odometer) { Console.WriteLine("EndShift"); odometer = 1; return 0; }

public class SomeNestedClass
{
public string NestedValue { get; set; }

public string NestedName { get; set; }

public void Hello()
{
Console.WriteLine("Hello Nested World");
}
}
}

Wrapped Classes:

public sealed class Driver
{
#region Constructors

public DataModels.Driver driverDataModel;
public Driver(DataModels.Driver driver) { this.driverDataModel = driver; }
public Driver(System.String name)
{
driverDataModel = new DataModels.Driver(name);
}
public Driver()
{
driverDataModel = new DataModels.Driver();
}

#endregion Constructors

#region Properties

public System.String DriverId { get { return driverDataModel.DriverId; }  }
public System.String DriverName { get { return driverDataModel.DriverName; } set { driverDataModel.DriverName = value; } }
public System.String TruckNo { get { return driverDataModel.TruckNo; } set { driverDataModel.TruckNo = value; } }
private BasicClassWrapperSample.SomeNestedClass _Nested = null;
public BasicClassWrapperSample.SomeNestedClass Nested { get { if(_Nested == null) _Nested = new BasicClassWrapperSample.SomeNestedClass(driverDataModel.Nested); return _Nested; } set { _Nested = value; } }
private BasicClassWrapperSample.User _UserInfo = null;
public BasicClassWrapperSample.User UserInfo { get { if(_UserInfo == null) _UserInfo = new BasicClassWrapperSample.User(driverDataModel.UserInfo); return _UserInfo; } set { _UserInfo = value; } }

#endregion Properties

#region Methods

public void StartShift (System.String p1, System.String p2, ref System.Int32 odometer, params System.Object[] args) => driverDataModel.StartShift(p1, p2, ref odometer, args);
public System.Int32 EndShift (System.String p1, System.String p2, out System.Int32 odometer) => driverDataModel.EndShift(p1, p2, out odometer);

#endregion Methods
}
public sealed class SomeNestedClass
{
#region Constructors

public DataModels.Driver.SomeNestedClass somenestedclassDataModel;
public SomeNestedClass(DataModels.Driver.SomeNestedClass somenestedclass) { this.somenestedclassDataModel = somenestedclass; }
public SomeNestedClass()
{
somenestedclassDataModel = new DataModels.Driver.SomeNestedClass();
}

#endregion Constructors

#region Properties

public System.String NestedValue { get { return somenestedclassDataModel.NestedValue; } set { somenestedclassDataModel.NestedValue = value; } }
public System.String NestedName { get { return somenestedclassDataModel.NestedName; } set { somenestedclassDataModel.NestedName = value; } }

#endregion Properties

#region Methods

public void Hello () => somenestedclassDataModel.Hello();

#endregion Methods
}

public sealed class User
{
#region Constructors

public DataModels.User userDataModel;
public User(DataModels.User user) { this.userDataModel = user; }
public User()
{
userDataModel = new DataModels.User();
}

#endregion Constructors

#region Properties

public System.String FirstName { get { return userDataModel.FirstName; } set { userDataModel.FirstName = value; } }
public System.String LastName { get { return userDataModel.LastName; } set { userDataModel.LastName = value; } }
public System.String UserName { get { return userDataModel.UserName; } set { userDataModel.UserName = value; } }
public System.Int32 Role { get { return userDataModel.Role; } set { userDataModel.Role = value; } }
public System.Int32 InvalidLoginAttempt { get { return userDataModel.InvalidLoginAttempt; } set { userDataModel.InvalidLoginAttempt = value; } }

#endregion Properties

#region Methods

#endregion Methods
}

Wrapping Model within ViewModel

//Model
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserName { get; set; }
public int Role { get; set; }
public int InvalidLoginAttempt { get; set; }
}

//Wrapped ViewModel
public sealed class UserViewModel : ViewModelBase
{
#region Constructors

public DataModels.User userDataModel;
public UserViewModel(DataModels.User user) { this.userDataModel = user; }
public UserViewModel()
{
userDataModel = new DataModels.User();
}

#endregion Constructors

#region Properties

public System.String FirstName { get { return userDataModel.FirstName; } set { userDataModel.FirstName = value; OnPropertyChanged(); } }
public System.String LastName { get { return userDataModel.LastName; } set { userDataModel.LastName = value; OnPropertyChanged(); } }
public System.String UserName { get { return userDataModel.UserName; } set { userDataModel.UserName = value; OnPropertyChanged(); } }
public System.Int32 Role { get { return userDataModel.Role; } set { userDataModel.Role = value; OnPropertyChanged(); } }
public System.Int32 InvalidLoginAttempt { get { return userDataModel.InvalidLoginAttempt; } set { userDataModel.InvalidLoginAttempt = value; OnPropertyChanged(); } }

#endregion Properties
}