modify a TT template to add required html element - t4

I am trying to create a t4 template to help speed up my Create Form template.
Is it possible to add extra html depending if a model's property is required?
e.g.
[Required]
[Display(Name = "Contact Email Address:")]
public string ContactEmailAddress { get; set; }
Now in my tt file do something like
foreach (ModelProperty property in GetModelProperties(mvcHost.ViewDataType)) {
if (!property.IsPrimaryKey && !property.IsReadOnly) {
#>
<div>
#Html.LabelFor(model => model.<#= property.Name #>)
#Html.EditorFor(model => model.<#= property.Name #>)
#Html.ValidationMessageFor(model => model.<#= property.Name #>)
if(this.Required==true){<span class="required-field"></span>}
</div>
<#
}
Or is this not possible?

1,Open this file:
C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\Extensions\Microsoft\Web\Mvc\Scaffolding\Templates\MvcViewWithContextScaffolder\ModelPropertyFunctions.include.t4
2, Add some property to ModelProperty
class ModelProperty {
public string Name { get; set; }
public string AssociationName { get; set; }
public string ValueExpression { get; set; }
public string ModelValueExpression { get; set; }
public string ItemValueExpression { get; set; }
public EnvDTE.CodeTypeRef Type { get; set; }
public bool IsPrimaryKey { get; set; }
public bool IsForeignKey { get; set; }//
public bool IsReadOnly { get; set; }//
public bool IsRequired{ get; set;}//Here is your customer Property
public bool Scaffold { get; set; }
}
3, Add an method out under this class
bool IsRequired(EnvDTE.CodeProperty propertyType)
{
foreach (EnvDTE.CodeAttribute attribute in propertyType.Attributes)
{
if (String.Equals(attribute.FullName, "System.ComponentModel.DataAnnotations.RequiredAttribute", StringComparison.Ordinal))
{
return true;
}
}
return false;
}
4,Go to the bottom of this file to modify the method GetEligibleProperties:
List<ModelProperty> GetEligibleProperties(EnvDTE.CodeType typeInfo) {
List<ModelProperty> results = new List<ModelProperty>();
if (typeInfo != null) {
foreach (var prop in typeInfo.GetPublicMembers().OfType<EnvDTE.CodeProperty>()) {
if (prop.HasPublicGetter() && !prop.IsIndexerProperty() && IsBindableType(prop.Type)) {
string valueExpression = GetValueExpressionSuffix(prop);
results.Add(new ModelProperty {
Name = prop.Name,
AssociationName = GetAssociationName(prop),
ValueExpression = valueExpression,
ModelValueExpression = "Model." + valueExpression,
ItemValueExpression = "item." + valueExpression,
Type = prop.Type,
IsPrimaryKey = IsPrimaryKey(prop),
IsForeignKey = IsForeignKey(prop),
IsRequired=IsRequired(prop),//Here is your customer property.
IsReadOnly = !prop.HasPublicSetter(),
Scaffold = Scaffold(prop)
});
}
}
}
return results;
}
5, go to the file
C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\Extensions\Microsoft\Web\Mvc\Scaffolding\Templates\MvcViewWithContextScaffolder\Edit.cs.t4
Add the following check before your ValidationMessageFor
#Html.ValidationMessageFor(model => model.<#= property.Name #>):
Codes like this:
<#
if (property.IsRequired) {
#>
*<!--your html code-->
<#
}
#>

This would be possible but would take more work than what you have here. You could add a Required property to ModelProperty and then set it by looking for the required attribute on the property when getting the model properties. You can take a look at the code that determines whether or not a property is a primary key in the .tt templates for an example.

If someone still searching for a solution...
I'm using the MetadataType attibute to define the property attributes like Required or DisplayName in a seperate class.
Sample:
[MetadataType(typeof(personMetaData))]
public partial class Person
{
}
public class personMetaData
{
[DisplayName("Surname")]
[Required]
public object Name { get; set; }
}
If you want to access these attributes in the t4-template you have to extend the ModelProperty class and creator in the template-file.
Put the following code at the bottom of your template (e.g. List.tt). You have to replace the existing code.
<#+
// Describes the information about a property on the model
public class ModelProperty
{
public string Name { get; set; }
public string ValueExpression { get; set; }
public Type UnderlyingType { get; set; }
public bool IsPrimaryKey { get; set; }
public bool IsReadOnly { get; set; }
public string DisplayName { get; set; }
}
// Change this list to include any non-primitive types you think should be eligible for display/edit
private static Type[] bindableNonPrimitiveTypes = new[]
{
typeof (string),
typeof (decimal),
typeof (Guid),
typeof (DateTime),
typeof (DateTimeOffset),
typeof (TimeSpan),
};
// Call this to get the list of properties in the model. Change this to modify or add your
// own default formatting for display values.
public List<ModelProperty> GetModelProperties(Type type)
{
List<ModelProperty> results = GetEligibleProperties(type);
foreach (ModelProperty prop in results)
{
if (prop.UnderlyingType == typeof (double) || prop.UnderlyingType == typeof (decimal))
{
prop.ValueExpression = "String.Format(\"{0:F}\", " + prop.ValueExpression + ")";
}
else if (prop.UnderlyingType == typeof (DateTime))
{
prop.ValueExpression = "String.Format(\"{0:g}\", " + prop.ValueExpression + ")";
}
}
return results;
}
// Call this to determine if the property represents a primary key. Change the
// code to change the definition of primary key.
private bool IsPrimaryKey(PropertyInfo property)
{
if (string.Equals(property.Name, "id", StringComparison.OrdinalIgnoreCase))
{
// EF Code First convention
return true;
}
if (string.Equals(property.Name, property.DeclaringType.Name + "id", StringComparison.OrdinalIgnoreCase))
{
// EF Code First convention
return true;
}
foreach (object attribute in property.GetCustomAttributes(true))
{
if (attribute is KeyAttribute)
{
// WCF RIA Services and EF Code First explicit
return true;
}
var edmScalar = attribute as EdmScalarPropertyAttribute;
if (edmScalar != null && edmScalar.EntityKeyProperty)
{
// EF traditional
return true;
}
/* var column = attribute as ColumnAttribute;
if (column != null && column.IsPrimaryKey)
{
// LINQ to SQL
return true;
}*/
}
return false;
}
// This will return the primary key property name, if and only if there is exactly
// one primary key. Returns null if there is no PK, or the PK is composite.
private string GetPrimaryKeyName(Type type)
{
IEnumerable<string> pkNames = GetPrimaryKeyNames(type);
return pkNames.Count() == 1 ? pkNames.First() : null;
}
// This will return all the primary key names. Will return an empty list if there are none.
private IEnumerable<string> GetPrimaryKeyNames(Type type)
{
return GetEligibleProperties(type).Where(mp => mp.IsPrimaryKey).Select(mp => mp.Name);
}
// Helper
private List<ModelProperty> GetEligibleProperties(Type type)
{
List<ModelProperty> results = new List<ModelProperty>();
foreach (PropertyInfo prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
Type underlyingType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType;
if (prop.GetGetMethod() != null && prop.GetIndexParameters().Length == 0 &&
IsBindableType(underlyingType))
{
var displayName = prop.Name;
// Search in Metadata
var metadata = type.GetCustomAttributes(typeof(MetadataTypeAttribute), true).OfType<MetadataTypeAttribute>().ToArray().FirstOrDefault();
if (metadata != null)
{
var metaPropery = metadata.MetadataClassType.GetProperty(prop.Name);
if (metaPropery != null)
{
displayName = ((DisplayNameAttribute)metaPropery.GetCustomAttributes(typeof (DisplayNameAttribute), true).First()).DisplayName;
}
}
results.Add(new ModelProperty
{
Name = prop.Name,
ValueExpression = "Model." + prop.Name,
UnderlyingType = underlyingType,
IsPrimaryKey = IsPrimaryKey(prop),
IsReadOnly = prop.GetSetMethod() == null,
DisplayName = displayName
});
}
}
return results;
}
// Helper
private bool IsBindableType(Type type)
{
return type.IsPrimitive || bindableNonPrimitiveTypes.Contains(type);
}
#>
Now you can access these attributes like this:
<#
List<ModelProperty> properties = GetModelProperties(mvcHost.ViewDataType);
foreach (ModelProperty property in properties) {
if (!property.IsPrimaryKey) {
#>
<th>
<#= property.DisplayName #><#= property.AllowEmptyStrings ? "*" : "" #>
</th>
<#
}
}
#>

T4 templates have changed with MVC5. To accomplish this in MVC5, I've written a tutorial here: https://johniekarr.wordpress.com/2015/05/16/mvc-5-t4-templates-and-view-model-property-attributes/

Related

Increment a value when conditions are met, otherwise set to zero

Given a list of strings, I'd like to create a list of the following object
class LineInfo
{
public string line { get; set; }
public bool isSearchMatch { get; set; }
public int searchMatchNumber { get; set; }
}
Where I'd like searchMatchNumber to have 1 for the 1st match, 2 for the 2nd, etc. Otherwise it can be zero
I set this up like so
IEnumerable<string> allLines; //pulled in from somewhere
IEnumerable<LineInfo> logInfoLines = allLines.Select((l, i) => new LineInfo
{
line = l,
isSearchMatch = l.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0
});
How can I set searchMatchNumber?
LINQ queries should not cause side effects like this. If you'd add an OrderBy you'd get a different result. I wouldn't use LINQ for this, you could provide a method:
class LineInfo
{
public string line { get; set; }
public bool isSearchMatch { get; set; }
public int searchMatchNumber { get; set; }
public static IEnumerable<LineInfo> GetSearchResult(IEnumerable<string> allLines, string search)
{
int matchCounter = 0;
var lineInfoList = new List<LineInfo>();
foreach (string line in allLines)
{
bool isMatch = line.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0;
var li = new LineInfo
{
line = line,
isSearchMatch = isMatch,
searchMatchNumber = isMatch ? ++matchCounter : -1 // or whatever
};
lineInfoList.Add(li);
}
return lineInfoList;
}
}

PetaPoco returns empty object

Inside my applocation, the petapoco poco returns an empty object (all values are null). Using the UI-O-Matic Nuget package inside my Umbraco 7.5.12.
The query i'm currently running:
var dbContext = ApplicationContext.Current.DatabaseContext;
var objects = dbContext.Database.Fetch<ObjectDB>("select Id, Name, CreatedOn, PlaceId, InActive, CityMapping, CountryIsoMapping, Globalsearch from ObjectsDB");
return objects.Where(n => n.PlaceId == PlaceId).FirstOrDefault();
TableDB is my PetaPoco model with the fields like:
[UIOMatic("ObjectsDB", "Object", "Object", FolderIcon = "icon-globe-inverted-europe-africa", ItemIcon = "icon-pin-location", RenderType = UIOMaticRenderType.List)]
[TableName("ObjectsDB")]
[PrimaryKey("Id", autoIncrement = false)]
[ExplicitColumns]
public class ObjectDB
{
[PrimaryKeyColumn(AutoIncrement = true)]
public int Id { get; set; }
[UIOMaticListViewFilter]
[UIOMaticListViewField(Name = "Name")]
[UIOMaticField(Name = "Name", Description = "Name")]
public string Name { get; set; }
}
When debuging:
`Debug result: con.Single<ObjectsDB>("select Name, Id from ObjectsDB where Id = 4")
This retruns the object:
{Umbraco.Extensions.Models.Custom.ObjectsModel.ObjectsDB} _createdOn: {1/1/0001 12:00:00 AM}
CityMapping: null
CountryIsoMapping: null
CreatedOn: {5/19/2017 4:22:16 PM}
Globalsearch: false
Id: 0
InActive: false
InCache: false
Name: null
Object: null
PlaceId: null `
Inserting data is working with the same dbContext, that's working.
What am I missing here?
I have used Petapoco in various Umbraco project and my approach is a bit different than your approach. I am sharing it here, hope it helps you.
This is the nuget package that I have used:(http://nuget.org/List/Packages/PetaPoco)
Please see my sample code below or in my blog:
[PetaPoco.TableName("fsCarts")]
[PetaPoco.PrimaryKey("RecordID")]
public class Cart
{
[Key]
public int RecordId { get; set; }
public string CartId { get; set; }
public Guid ProductId { get; set; }
public int Count { get; set; }
public DateTime DateCreated { get; set; }
}
UmbracoDatabase con = ApplicationContext.Current.DatabaseContext.Database;
public void AddToCart(Product product)
{
try
{
var cartItem = con.FirstOrDefault<Cart>("SELECT * FROM fsCarts WHERE CartID=#0 AND ProductID=#1", ShoppingCardId, product.ProductId);
if (cartItem == null)
{
cartItem = new Cart
{
ProductId = product.ProductId,
CartId = ShoppingCardId,
Count = 1,
DateCreated = DateTime.Now
};
con.Insert("fsCarts", "RecordID", cartItem);
}
else
{
cartItem.Count++;
con.Update("fsCarts", "RecordID", cartItem);
}
}
catch (Exception ex)
{
Elmah.ErrorLog.GetDefault(null).Log(new Elmah.Error(new Exception("Shopping Cart AddToCart: " + ex.ToString())));
}
}
////////////////////
public int RemoveFromCart(int id)
{
int itemCount = 0;
try
{
var cartItem = con.FirstOrDefault<Cart>("SELECT * FROM fsCarts WHERE CartID=#0 AND RecordId=#1", ShoppingCardId, id);
if (cartItem != null)
{
if (cartItem.Count > 1)
{
cartItem.Count--;
itemCount = cartItem.Count;
con.Update("fsCarts", "RecordID", cartItem);
}
else
{
con.Delete("fsCarts", "RecordID", cartItem);
}
}
}
catch (Exception ex)
{
Elmah.ErrorLog.GetDefault(null).Log(new Elmah.Error(new Exception("Shopping Cart RemoveFromCart: " + ex.ToString())));
}
return itemCount;
}
////////////////////
public List<Cart> GetCartItems()
{
List<Cart> cartItemList = new List<Cart>();
try
{
cartItemList = con.Query<Cart>("SELECT * FROM fsCarts WHERE CartID=#0", ShoppingCardId).ToList();
}
catch (Exception ex)
{
Elmah.ErrorLog.GetDefault(null).Log(new Elmah.Error(new Exception("Shopping Cart GetCartItems: " + ex.ToString())));
}
return cartItemList;
}
////////////////////
public decimal GetTotal()
{
decimal? total = null;
try
{
total = con.ExecuteScalar<decimal>("SELECT SUM(ISNULL(p.Price,0)*c.Count) FROM fsCarts c INNER JOIN fsProducts p ON c.ProductID=p.ProductID WHERE c.CartID=#0", ShoppingCardId);
}
catch (Exception ex)
{
Elmah.ErrorLog.GetDefault(null).Log(new Elmah.Error(new Exception("Shopping Cart GetTotal: " + ex.ToString())));
}
return total ?? decimal.Zero;
}
Removing the attribute [ExplicitColumns] above my class fixed the problem. No everything works as expected. Also the other decorations are working. So #Nurhak Kaya was partially right. After removing that attribute deleting the table and rebuild / generating the table.

Dynamic LINQ: Comparing Nested Data With Parent Property

I've a class with following structure:
public class BestWayContext
{
public Preference Preference { get; set; }
public DateTime DueDate { get; set; }
public List<ServiceRate> ServiceRate { get; set; }
}
public class ServiceRate
{
public int Id { get; set; }
public string Carrier { get; set; }
public string Service { get; set; }
public decimal Rate { get; set; }
public DateTime DeliveryDate { get; set; }
}
and I've dynamic linq expression string
"Preference != null && ServiceRate.Any(Carrier == Preference.Carrier)"
and I want to convert above string in Dynamic LINQ as follows:
var expression = System.Linq.Dynamic.DynamicExpression.ParseLambda<BestWayContext, bool>(condition, null).Compile();
But it showing following error:
Please correct me what am I doing wrong?
It looks like you wanted to do something like this:
var bwc = new BestWayContext
{
Preference = new Preference { Carrier = "test" },
DueDate = DateTime.Now,
ServiceRate = new List<ServiceRate>
{
new ServiceRate
{
Carrier = "test",
DeliveryDate = DateTime.Now,
Id = 2,
Rate = 100,
Service = "testService"
}
}
};
string condition = "Preference != null && ServiceRate.Any(Carrier == #0)";
var expression = System.Linq.Dynamic.DynamicExpression.ParseLambda<BestWayContext, bool>(condition, bwc.Preference.Carrier).Compile();
bool res = expression(bwc); // true
bwc.ServiceRate.First().Carrier = "test1"; // just for testing this -> there is only one so I've used first
res = expression(bwc); // false
You want to use Preference which belong to BestWayContext but you didn't tell the compiler about that. If i write your expression on Linq i will do as follows:
[List of BestWayContext].Where(f => f.Preference != null && f.ServiceRate.Where(g => g.Carrier == f.Preference.Carrier)
);
As you see i specified to use Preference of BestWayContext.

Getting an Enum to display on client side

I'm having hard time understanding how to convert an Enum value to it's corresponding name. My model is as follows:
public class CatalogRule
{
public int ID { get; set; }
[Display(Name = "Catalog"), Required]
public int CatalogID { get; set; }
[Display(Name = "Item Rule"), Required]
public ItemType ItemRule { get; set; }
public string Items { get; set; }
[Display(Name = "Price Rule"), Required]
public PriceType PriceRule { get; set; }
[Display(Name = "Value"), Column(TypeName = "MONEY")]
public decimal PriceValue { get; set; }
[Display(Name = "Exclusive?")]
public bool Exclude { get; set; }
}
public enum ItemType
{
Catalog,
Category,
Group,
Item
}
public enum PriceType
{
Catalog,
Price_A,
Price_B,
Price_C
}
A sample result from .net API:
[
{
$id: "1",
$type: "XYZ.CMgr.Models.CatalogRule, XYZ.CMgr",
ID: 1,
CatalogID: 501981,
ItemRule: 0,
Items: "198",
PriceRule: 1,
PriceValue: 0.5,
Exclude: false
},
{
$id: "2",
$type: "XYZ.CMgr.Models.CatalogRule, XYZ.CMgr",
ID: 2,
CatalogID: 501981,
ItemRule: 2,
Items: "9899",
PriceRule: 2,
PriceValue: 10.45,
Exclude: false
}
]
So in this example, I need to get Catalog for results[0].ItemRule & Price A for results[0].PriceRule. How can I accomplish this in BreezeJS??
This is easy to do in ASP.NET Web API, because it is an out-of-box feature in the default JSON serializer (Json.NET).
To see strings instead of enum numbers in JSON, just add an instance of StringEnumConverter to JSON serializer settings during app init:
var jsonFormatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
UPDATE: Yep, you right, this is not help with Breeze.js. Ok, you can anyway do a little magic to make enums work like strings (while new version with fix is not released).
Create a custom ContextProvider which updates all integer enum values in metadata to strings. Here it is:
public class StringEnumEFContextProvider<T> : EFContextProvider<T>
where T : class, new()
{
protected override string BuildJsonMetadata()
{
XDocument xDoc;
if (Context is DbContext)
{
xDoc = GetCsdlFromDbContext(Context);
}
else
{
xDoc = GetCsdlFromObjectContext(Context);
}
var schemaNs = "http://schemas.microsoft.com/ado/2009/11/edm";
foreach (var enumType in xDoc.Descendants(XName.Get("EnumType", schemaNs)))
{
foreach (var member in enumType.Elements(XName.Get("Member", schemaNs)))
{
member.Attribute("Value").Value = member.Attribute("Name").Value;
}
}
return CsdlToJson(xDoc);
}
}
And use it instead of EFContextProvider in your Web API controllers:
private EFContextProvider<BreezeSampleContext> _contextProvider =
new StringEnumEFContextProvider<BreezeSampleContext>();
This works well for me with current Breeze.js version (1.1.3), although I haven't checked other scenarios, like validation...
UPDATE: To fix validation, change data type for enums in breeze.[min|debug].js, manually (DataType.fromEdmDataType function, dt = DataType.String; for enum) or replace default function during app init:
breeze.DataType.fromEdmDataType = function (typeName) {
var dt = null;
var parts = typeName.split(".");
if (parts.length > 1) {
var simpleName = parts[1];
if (simpleName === "image") {
// hack
dt = DataType.Byte;
} else if (parts.length == 2) {
dt = DataType.fromName(simpleName);
if (!dt) {
if (simpleName === "DateTimeOffset") {
dt = DataType.DateTime;
} else {
dt = DataType.Undefined;
}
}
} else {
// enum
dt = DataType.String; // THIS IS A FIX!
}
}
return dt;
};
Dirty, dirty hacks, I know... But that's the solution I found
There will be a new release out in the next few days where we "change" breeze's enum behavior ( i.e. break existing code with regards to enums). In the new release enums are serialized and queried by their .NET names instead of as integers. I will post back here when the new release is out.

Remote validation not working for guid

[Remote("DropDownSelected", "Patient")]
public Guid SexIdentifier { get; set; }
public ActionResult DropDownSelected(object value)
{
var x = ((Guid)value).ToString();
string xsd = value.ToString();
var abc = ControllerContext.Controller;
if (value == null)
{
//value = string.Empty;
}
if (value.ToString() == Convert.ToString(Guid.Empty) || value.ToString() == string.Empty)
{
return Json(String.Format("0 does not exist."), JsonRequestBehavior.AllowGet);
}
return Json(true, JsonRequestBehavior.AllowGet);
}
If your property is called SexIdentifier then your action must use the same name for its argument:
public ActionResult DropDownSelected(Guid sexIdentifier)
{
...
}
Also if you have a default value of the dropdown you could use a nullable Guid:
public ActionResult DropDownSelected(Guid? sexIdentifier)
{
...
}

Resources