Skip to content

Instantly share code, notes, and snippets.

@astrohart
Last active January 30, 2021 14:38
Show Gist options
  • Save astrohart/9a1e13af51df339bfc13054828ff7653 to your computer and use it in GitHub Desktop.
Save astrohart/9a1e13af51df339bfc13054828ff7653 to your computer and use it in GitHub Desktop.
T4 Template that creates an ObservableListSource in the <project-name>.DataBinding Namespace. This is what Navigation Properties should use for EF6+WinForms compatibility.
<#@ template language="C#" debug="false" hostspecific="true" #>
<#@ output extension=".cs" #>
<#@ assembly name="System" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Data.Entity" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Reflection" #>
<#@ assembly name="EnvDTE" #>
<#@ import namespace="EnvDTE" #>
<#@ assembly name="System.Data.Entity.Design" #>
<#@ import namespace="System.Data.Entity.Design.PluralizationServices" #>
<#
// TODO: Place calls here to class-feature method calls.
InitializeActiveProjectAndSolution();
#>
<#= GenerateFileHeader()#>
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data.Entity;
// ReSharper disable once CheckNamespace
namespace <#=this.DataBindingLayerNameSpace#>
{
<# PushIndent(" "); #>
/// <summary>
/// This is a collection that is compatible with Windows Forms' data-bound
/// controls, as it allows Windows Forms graphical, data-bound presentation
/// controls to engage in two-way communications with memory objects. Please
/// also derive all your entities' navigation properties from this class by
/// modifying the Entities.Context.tt and Entities.tt T4 templates in your
/// Data Access Layer.
/// </summary>
/// <typeparam name="T">Name of the entity object being wrapped by this collection.</typeparam>
public class ObservableListSource<T> : ObservableCollection<T>, IListSource
where T : class
{
<# PushIndent(" "); #>
/// <summary>
/// Constructs a new instance of
/// <see cref="T:<#=this.DataBindingLayerNameSpace#>.ObservableListSource" /> and returns a
/// reference to it.
/// </summary>
public ObservableListSource() { }
/// <summary>
/// Constructs a new instance of
/// <see cref="T:<#=this.DataBindingLayerNameSpace#>.ObservableListSource" /> and returns a
/// reference to it.
/// </summary>
/// <param name="collection">
/// Enumerable collection of objects of
/// <typeparamref name="T" />.
/// </param>
public ObservableListSource(IEnumerable<T> collection) : base(collection) { }
/// <summary>
/// Gets a value indicating whether the collection is a collection of
/// <see cref="T:System.Collections.IList" /> objects.
/// </summary>
/// <returns>
/// <see langword="true" /> if the collection is a collection of
/// <see cref="T:System.Collections.IList" /> objects; otherwise,
/// <see langword="false" />.
/// </returns>
bool IListSource.ContainsListCollection
{
get { return false; }
}
/// <summary>
/// Gets a reference to an instance of this collection that implements the
/// <see cref="T:System.ComponentModel.IBindingList" /> interface.
/// </summary>
public IBindingList List { get; private set; }
/// <summary>
/// Returns an <see cref="T:System.Collections.IList" /> that can be bound
/// to a data source from an object that does not implement an
/// <see cref="T:System.Collections.IList" /> itself.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IList" /> that can be bound to a
/// data source from the object.
/// </returns>
IList IListSource.GetList()
{
return List ?? (List = this.ToBindingList());
}
<# PopIndent(); #>
}
<# PopIndent(); #>
}
<# CreateFile("ObservableListSource.cs"); #>
<#+
/// <summary>
/// Generates a header comment for the file so that users of the file know that it's generated from a template.
/// </summary>
public string GenerateFileHeader()
{
return "//------------------------------------------------------------------------------\r\n// <auto-generated>\r\n// This code was generated from a template.\r\n//\r\n// Manual changes to this file may cause unexpected behavior in your application.\r\n// Manual changes to this file will be overwritten if the code is regenerated.\r\n// </auto-generated>\r\n//------------------------------------------------------------------------------";
}
/// <summary>
/// Gets or sets a reference to the currently-active project in the integrated development environment (IDE).
/// </summary>
private Project ActiveProject { get; set; }
/// <summary>
/// Gets or sets a reference to the currently-active solution in the integrated development environment (IDE).
/// </summary>
private Solution ActiveSolution { get; set; }
/// <summary>
/// Gets or sets a reference to the top-level object in the Visual Studio automation object model.
/// </summary>
private DTE DTE { get; set; }
/// <summary>
/// Gets a string that contains the C# namespace name for the business layer.
/// </summary>
/// <remarks>
/// The business layer is the tier of your solution that contains classes generated from this T4 template.
/// </remarks>
private string BusinessLayerNameSpace {
get {
return ActiveProject == null ? string.Empty : string.Format(
"{0}.BusinessLayer", ActiveProject.Name
);
}
}
/// <summary>
/// Gets a string that contains the C# namespace name for the data-binding layer.
/// </summary>
/// <remarks>
/// The data-binding layer is where we implement useful classes that are utilized by
/// Windwos Forms for data-binding.
/// </remarks>
private string DataBindingLayerNameSpace {
get {
return ActiveProject == null ? string.Empty : string.Format(
"{0}.DataBinding", ActiveProject.Name
);
}
}
/// <summary>
/// Interrogates the Visual Studio object model to locate references to
/// the currently-active Solution and Project.
/// </summary>
public void InitializeActiveProjectAndSolution()
{
IServiceProvider serviceProvider = (IServiceProvider)this.Host;
this.DTE = serviceProvider.GetService(typeof(DTE)) as DTE;
ActiveSolution = this.DTE.Solution;
ActiveProject = GetActiveProject();
}
//Generating Seperate Files
public void ProcessContent(string outputFileName, string content)
{
if (string.IsNullOrWhiteSpace(outputFileName)) throw new ArgumentNullException(nameof(outputFileName));
if (string.IsNullOrWhiteSpace(content)) throw new ArgumentNullException(nameof(content));
var templateDirectory = Path.GetDirectoryName(Host.TemplateFile);
var outputFilePath = Path.Combine(templateDirectory, outputFileName);
var outputDirectoryPath = Path.GetDirectoryName(outputFilePath);
if(!Directory.Exists(outputDirectoryPath))
Directory.CreateDirectory(outputDirectoryPath);
if (File.Exists(outputFilePath)) // always overwrite existing output
File.Delete(outputFilePath);
File.WriteAllText(outputFilePath, content);
IServiceProvider hostServiceProvider = (IServiceProvider)Host;
EnvDTE.DTE dte = (EnvDTE.DTE)hostServiceProvider.GetService(typeof(EnvDTE.DTE));
ProjectItem containingProjectItem = dte.Solution.FindProjectItem(Host.TemplateFile);
containingProjectItem.ProjectItems.AddFromFile(outputFilePath);
}
public string GetDataSourceName(string efContext)
{
if (string.IsNullOrWhiteSpace(efContext)) throw new ArgumentNullException(nameof(efContext));
return efContext
.Replace("Entities", "")
.Replace("DbContext", "")
.Replace("Db", "")
.Replace("Context", "");
}
public void CreateFile(string fileName)
{
if (string.IsNullOrWhiteSpace(fileName)) throw new ArgumentNullException(nameof(fileName));
ProcessContent(fileName, this.GenerationEnvironment.ToString().TrimStart());
this.GenerationEnvironment.Remove(0, this.GenerationEnvironment.Length);
}
//Get Projects
public Project GetActiveProject()
{
Project activeProject = null;
Array activeSolutionProjects = this.DTE.ActiveSolutionProjects as Array;
if (activeSolutionProjects != null && activeSolutionProjects.Length > 0)
activeProject = activeSolutionProjects.GetValue(0) as Project;
return activeProject;
}
public List<CodeClass> FindClasses(Project project, string ns, string className)
{
List<CodeClass> result = new List<CodeClass>();
FindClasses(project.CodeModel.CodeElements, className, ns, result, false);
return result;
}
private void FindClasses(CodeElements elements, string className, string searchNamespace, List<CodeClass> result, bool isNamespaceOk)
{
if (elements == null) return;
foreach (CodeElement element in elements)
{
if (element is CodeNamespace)
{
CodeNamespace ns = element as CodeNamespace;
if (ns != null)
{
if (ns.FullName == searchNamespace)
FindClasses(ns.Members, className, searchNamespace, result, true);
else
FindClasses(ns.Members, className, searchNamespace, result, false);
}
}
else if (element is CodeClass && isNamespaceOk)
{
CodeClass c = element as CodeClass;
if (c != null)
{
if (c.FullName.Contains(className))
result.Add(c);
FindClasses(c.Members, className, searchNamespace, result, true);
}
}
}
}
//Naming
public string GetProperClassName(string className)
{
string returnString = className;
returnString = returnString.Replace(" ", "_");
returnString = returnString.Replace("_", " ");
//TextInfo ti = CultureInfo.CurrentCulture.TextInfo;
//returnString = ti.ToTitleCase(returnString);
returnString = returnString.Replace(" ", "");
return returnString;
}
private static string CharToUpper(string input, int position)
{
return input.First().ToString().ToUpper() + input.Substring(position+1);
}
#>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment