I need to easily create/automate some boilerplate code from a simple class in C#/.NET. I have found the T4 templating engine, but I am hoping someone can perhaps give some guidence on how best to implement and if I am on the right track.
I have classes that all look very similar to this:
using System;
using System.Collections.Generic;
namespace BarryAPI.Api
{
public partial class Client
{
public int Id { get; set; }
public int OrganId { get; set; }
public string Title { get; set; } = null!;
public string FirstName { get; set; } = null!;
public string LastName { get; set; } = null!;
public DateTime DateOfBirth { get; set; }
public string Gender { get; set; } = null!;
public int AddressId { get; set; }
public string? Phone { get; set; }
public string? Mobile { get; set; }
}
}
And I am trying to (mostly) automate the creation of a matched class that looks like this:
using System.ComponentModel.DataAnnotations.Schema;
using Barry.Domain.Entities.Client.Email;
using Barry.Domain.Entities.Organisation;
using Barry.Infrastructure;
namespace Barry.Domain.Entities.Client;
[Table("Client")]
public class ClientEntity : Entity, IMapFrom<ClientDto>
{
public const int TitleMaxLength = 50;
public const int FirstNameMaxLength = 100;
public const int LastNameMaxLength = 100;
public const int GenderMaxLength = 20;
public const int PhoneMaxLength = 20;
public const int MobileMaxLength = 60;
#nullable disable
public ClientEntity()
{
}
#nullable restore
public string Title { get; set; } = null!;
public string FirstName { get; set; } = null!;
public string LastName { get; set; } = null!;
public DateTime DateOfBirth { get; set; }
public string Gender { get; set; } = null!;
public int AddressId { get; set; }
public string? Phone { get; set; }
public string? Mobile { get; set; }
}
I understand I cannot make all the logic decisions in the template (Like MaxLength etc), but I am hoping to automate the bulk of it as there as many classes that need to be converted into entity models.
I have looked into T4, but I am struggling to know if this is the correct choice. Can anyone please shed a little light on this problem and if I am headed down the right path trying to solve it with T4?
I've been tasked with writing something up to semi-automate this process (mostly just to automate the typing/copy/paste by hand) and I am not sure if a simple console app to read the file and output what's required would be more efficient? Would it?
If you think T4 is the go, pointing towards a similar case tutorial/docs that you know of would be a huge help.
Thank you.
T4 templates and execution subsystem not designed to EDIT any of existing files - T4 used to generate whole file and replace existing one.
You can put any logic you want in T4-template or in side DLL's used by the template. Any, including any kind of parsing existed C#/VB/F#&etc files. Just pay attention to complexity of T4-template - it difficult to support when pieces of text mixed with complex code.
Related
I scaffolded my database succesfully, and I tried adding a field to a model
`
public partial class Cotizaciones
{
private static Random rnd = new Random();
public Cotizaciones()
{
DetalleProductoPersonalizados = new HashSet<DetalleProductoPersonalizado>();
}
[Key]
public int Idcotizacion { get; set; }
public DateTime FechaInicio { get; set; }
public DateTime FechaFin { get; set; }
public double PrecioFinal { get; set; }
public string Ubicacion { get; set; } = null!;
public bool Estado { get; set; }
public int? PaqueteFk { get; set; }
[Column(TypeName = "nvarchar(max)")]
public string? NombreCotizacion = GenerateLetter(); //---> new field
private static string GenerateLetter()
{
StringBuilder fileName = new StringBuilder("");
for (int i = 0; i <= rnd.NextInt64(1,35); i++)
{
fileName.Insert(i, Convert.ToChar(rnd.Next(65, 90)));
}
return fileName.ToString();
}
[NotMapped]
[DisplayName("Subir comprobante de pago")]
public IFormFile ImageFile { get; set; }
public virtual Paquete? PaqueteFkNavigation { get; set; }
public virtual ICollection<DetalleProductoPersonalizado> DetalleProductoPersonalizados { get; set; }
}
`
However applying migrations said no changes were made, making a new migration and trying to apply it throws me this message
There is already an object named 'AspNetRoles' in the database.
You are mixing Model stuff, with business logic, and EF will not allow this. You would need to take the "GenerateLetter" piece and move it to a different process, and make your new addition a true property.
You could possibly use a [Backing Fields][1] implementation to try and get this working, but it will not do what I think you might think it would do.
You will most likely have to re-think how that GenerateLetter method is called if you want to persist the value to the database. You could possibly make it a [computed column][1], but you wouldn't have access to Random etc. there.
I am trying to create a ASP.NET application, and am using DataAnnotations in the Entity Class Models for more readable display names:
In my ApplicationDomain Project
public class Car{
public int Id { get; set; }
[Display(Name = "Make of Car")]
public string Make { get; set; }
[Display(Name = "Year of Purchase")]
public int PurchaseYear { get; set; }
}
When I then use this as the model for my views, everything is displayed as expected.
But when I use a view model, I then have to add the Annotations again as the Display Name I initially added to Car is not 'Inherited' to the View Models based on it.
In my WebMVC Project
public class EditCarViewModel{
[Display(Name = "Make of Car")]
public string Make { get; set; }
[Display(Name = "Year of Purchase")]
public int PurchaseYear { get; set; }
}
The same for the create, index and any other views that use a viewmodel and not the Car Class.
Is there anyway to have the annotations that are in the initial entity class model inherited / propagated up, into the related view models so I'm not having to do this in multiple places?
I think this will be even more of an issue if I then try to add a different UI project. e.g. a desktop application in addition to the WebMVC.
It would be ideal if the labels for both could be based on the definitions in the ApplicationDomain Project.
You can try creating a new metadata class and apply it to your others.
[MetadataType(typeof(CarModelMetaData))]
public class EditCarViewModel{
public string Make { get; set; }.
public int PurchaseYear { get; set; }
}
[MetadataType(typeof(CarModelMetaData))]
public class CreateCarViewModel{
public string Make { get; set; }
public int PurchaseYear { get; set; }
}
public class CarModelMetaData{
[Display(Name = "Make of Car")]
public string Make { get; set; }
[Display(Name = "Year of Purchase")]
public int PurchaseYear { get; set; }
}
There is no way of propagating annotation text from one class to another.
But if you just want to keep the same text in one place, you can create constants and use them this way:
public static class DisplayConstants
{
public const string Make = "Make of Car";
public const string PurchaseYear = "Year of Purchase";
}
public class EditCarViewModel{
[Display(Name = DisplayConstants.Make)]
public string Make { get; set; }
[Display(Name = DisplayConstants.PurchaseYear)]
public int PurchaseYear { get; set; }
}
public class Car
{
public int Id { get; set; }
[Display(Name = DisplayConstants.Make)]
public string Make { get; set; }
[Display(Name = DisplayConstants.PurchaseYear)]
public int PurchaseYear { get; set; }
}
Note that this way you can name properties in EditCarViewModel and Car whatever way you like, no restriction on consistent naming.
I've been stuck with this for a while and I can't seem to figure it out. Appreciate any help!
This is my model: http://www.jsoneditoronline.org/?id=9ee3466c40627f33c284e63544c8b8a7
I have the proper C# objects set up like this:
public class Media
{
public string name { get; set; }
public string title { get; set; }
public string album { get; set; }
public string artist { get; set; }
public string length { get; set; }
public int bitrate { get; set; }
public double size { get; set; }
public string start_time { get; set; }
public string mimetype { get; set; }
public string hash { get; set; }
}
public class Playlist
{
public string name { get; set; }
public List<Media> media { get; set; }
public List<Graphics> graphics { get; set; }
public bool shuffle { get; set; }
public int volume { get; set; }
public string start_time { get; set; }
public string end_time { get; set; }
}
public class Day
{
public string name { get; set; }
public List<Playlist> playlists { get; set; }
}
public class Schedule
{
public List<Day> days { get; set; }
public string hash { get; set; }
}
I need to POST this whole JSON object directly from the MVC Controller. On other occasions I'd like to PUT the schedule. How can I properly handle this? Examples could really help.
Thanks!
I'm already doing the below for POST:
var schedule = JsonConvert.DeserializeObject<Schedule>(model.ToString());
This is working as expected however, sometimes related Media objects already exist in the database and it is causing an Internal Server Error when trying to INSERT the same Media object (which already exists) - The [Key] for Media is the hash property.
You need to serialize Day class.
using Newtonsoft.json nuget package you need to serialize it as object. It will automatically serialize complex object to json
List<Day> days = // list of days result
var jsonData= JsonConvert.SerializeObject(days);
return json(jsonData);
Update
As per your update serialize and deserialize functions are working properly. You are facing issue while inserting records in Media.
Hash is not unique. and Hash Collision is possible. You need to improve hash generation code to use hash as identical.
Useful links to understand hash
what is hash ? is it unique?
Can 2 different string have the same hash code in C#
Socks, birthdays and hash collisions
Preventing Duplicates: Hash Table vs. Dictionary vs. Binary Search
Tree
You can use the extension method AddOrUpdate
using System.Data.Entity.Migrations;
db.Schedule.AddOrUpdate(schedule)
Many a times i have to do select required fields from a class as in case of selected desired fields from Table in sql.
So i was thinking of making my class and struct as Projectables.
Here is what I want to achieve.
public class Email
{
public string Subject { get; set; }
public string To { get; set; }
public string Cc { get; set; }
public string Bcc { get; set; }
public string Body { get; set; }
public DateTime ReceivedDate { get; set; }
}
public class EmailProjection
{
// instead of this
//public string Subject { get; set; }
//public DateTime ReceivedDate { get; set; }
// I want to do this
Email.Project(Subject,ReceivedDate);
}
public class EntryPoint
{
static void Main()
{
var emailList = new List<Email>();
/* populate emails here from source*/
// I want to do this
List<EmailProjection> emailProjectionList = emailList.Project(EmailProjection).ToList();
// rather than
List<EmailProjection> emailProjectionList= emailList.Select(email=>new {email.Subject,email.ReceivedDate}).ToList();}
}
Can somebody help me as to how to go about this? Hints?? Is it achievable?
You can have a look at Automapper: https://github.com/AutoMapper/AutoMapper.
It is a library to map DTO based on naming conventions and rules.
Take a look in the wiki there is a Projection page, that could be the one you are looking for.
Just for connect with your example, you can eventually write code like this:
Mapper.Map<EMail, EmailProjection>(yourObjects);
I have created the following class, it describes any type of file that may be uploaded by a user:
namespace MyModels.Models
{
public class File
{
public string FileName { get; set; }
public string FileTypeId { get; set; }
public string URLFileName { get; set; } //cleaned for web
public string Dir { get; set; } //which directory is located in
public long FileSize { get; set; } //size in bytes
public DateTime DateAdded { get; set; } //date uploaded
public int UploadedByUser {get; set;} //UserID of user
public bool inCloud { get; set; } // moved to cloud
public bool inGlacier { get; set; } // moved to glaciers
public DateTime? DateTrashed { get; set; } //date user deleted
public int TrashedByUser { get; set; } //UserID of user
public List<FileDescendent> Descendents { get; set; } //List of copies
}
}
I then want to create a class for just image files. I want this class to have the file properties as well. Do I do this?
namespace MyModels.Models
{
public class Image : File
{
public int OrigHeight { get; set; }
public int OrigWidth { get; set; }
}
}
Or this?
namespace MyModels.Models
{
public class Image
{
public int OrigHeight { get; set; }
public int OrigWidth { get; set; }
public File File { get; set; }
}
}
What is the difference please?
Thanks!
This is an inheritance. In this case image is a file.
public class Image : File
{
public int OrigHeight { get; set; }
public int OrigWidth { get; set; }
}
This is a composition. In this case image has a file.
public class Image
{
public int OrigHeight { get; set; }
public int OrigWidth { get; set; }
public File File { get; set; }
}
As for me, image is a file. Thus inheritance is more appropriate here.
I think the difference is in the API. Would you rather look up inCloud as image.inCloud or image.File.inCloud?
In most cases, I'd prefer image.inCloud. However, if File is expensive to instantiate, for example, the second accessor pattern might be preferred.
I'd go with inheritance. It would allow you to use dependency injection models for both. Something that is very useful with MVC3.
the second big advantage, is that you can have shared views that display lists of Files, and you would be able to pass in collections of both types. Not to mention any Linq your write.
Your first example is inheritance, the second example is not.
In your second example you will have the file properties also, but bundled in the File property. To access the FileName property for example, you would use obj.File.FileName instead of obj.FileName that you get when you inherit the class.
Also, as File is a property in the Image class, it won't automatically get created when you create an instance of the Image class. You also have to create an instance of the File class and put in the File property of the Image instance.