Set format of cells produced by LoadFromCollection (epplus) using data annotation - data-annotations

I am using eeplus to create an excel spreadsheet, like this
using (var pck = new ExcelPackage())
{
var ws = pck.Workbook.Worksheets.Add("Customers");
ws.Cells["A1"].LoadFromCollection(customers, PrintHeaders: true);
var ms = new System.IO.MemoryStream();
pck.SaveAs(ms);
ms.WriteTo(Response.OutputStream);
}
The customer class has properties like
[DisplayName("Customer creation date")]
public DateTime Created { get; set; }
DisplayName seems to get honored, so the topmost line will read Customer creation date but the cell contents show up as 43257,41667.
What I would really like to have is cells that has the format 2018-04-05.
Can I do that will data annotations? I tried both
[DisplayName("Customer creation date")]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}")]
public DateTime Created { get; set; }
and
[DisplayName("Customer creation date")]
[DataType(DataType.Date)]
public DateTime Created { get; set; }
but the cell contents remains the same.

No, EPPlus doesnot format your data according to data annotations.
It formats date as integers, so you should specify the column you wish to format as
ws.Column(colPosition+1).Style.Number.Format="yyyy-mm-dd";
You can find details here:
https://github.com/JanKallman/EPPlus/wiki/Formatting-and-styling
https://codereview.stackexchange.com/questions/139569/ensuring-specific-columns-in-an-excelworksheet-format-as-shortdate

EPPlus always changed column name while updating into excel based upon DisplayName Attribute else if there is no DisplayName Attribute is set, then it will Find "_" (underscore) character & replace it with " " (Space) Character in the column name, Due to which we cannot easily find PropertyInfo with help of column name to format the column as per our need.
Here is the simple & quickest solution to format column based upon indexing the PropertyInfo
PropertyInfo[] props = typeof(T).GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
for (int i = 0; i < props.Length; i++)
{
Type t = props[i].PropertyType;
if (t == typeof(DateTime) || t == typeof(DateTime?))
ws.Column(i + 1).Style.Numberformat.Format = "dd-MMM-yyyy HH:mm:ss";
else if (t == typeof(TimeSpan) || t == typeof(TimeSpan?))
ws.Column(i + 1).Style.Numberformat.Format = "HH:mm:ss";
}
I have another solution if you need to format columns based upon column names.
void ApplyDateTimeFormatting<T>(ExcelWorksheet ws, IEnumerable<T> data)
{
if (data.Count() == 0)
return;
Type type = data.First().GetType();
for (int c = 1; c <= toColumns; c++)
{
string column = ws.Cells[1, c].Text;
var t = type.GetPropertyWithDisplayName<T>(column).PropertyType;
if (t == typeof(DateTime) || t == typeof(DateTime?))
ws.Column(c).Style.Numberformat.Format = "dd-MMM-yyyy HH:mm:ss";
else if (t == typeof(TimeSpan) || t == typeof(TimeSpan?))
ws.Column(c).Style.Numberformat.Format = "HH:mm:ss";
}
}
PropertyInfo GetPropertyFromDisplayName(Type type, string DisplayName)
{
MemberInfo[] members = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var member in members)
{
DisplayNameAttribute displayNameAttribute = member
.GetCustomAttributes(typeof(DisplayNameAttribute), inherit: false)
.FirstOrDefault() as DisplayNameAttribute;
string text = ((displayNameAttribute == null) ? member.Name.Replace('_', ' ') :
displayNameAttribute.DisplayName);
if (text == DisplayName)
return type.GetProperty(member.Name);
}
return null;
}

I solved it as follows, so I just load the model and change as per my model if it is int or datetime
var li = typeof(Model).GetProperties().ToArray();
using (var package = new ExcelPackage(stream))
{
var workSheet = package.Workbook.Worksheets.Add("Sheet1");
var i = 0;
foreach (var c in li)
{
i++;
if(c.PropertyType.Name == typeof(DateTime).Name || c.PropertyType.Name == typeof(DateTime?).Name)
workSheet.Column(i).Style.Numberformat.Format = DateTimeFormatInfo.CurrentInfo.ShortDatePattern; ;
if (c.PropertyType.Name == typeof(int).Name || c.PropertyType.Name == typeof(int?).Name)
workSheet.Column(i).Style.Numberformat.Format = "0";
}
}

Related

Need timezone based on the ISO country code and city in Java

My requirement
::: To find the timezone based on the city and country code both. Please share Is there any way to find it? I don't want to go with city only since one city can exist in two or more countries.
And even on city bases - I can find time zone but not for all cities if i am entering Leeds (UK city) , its giving nothing.
Set<String> availableTimeZones = ZoneId.getAvailableZoneIds();
String cityName = Normalizer.normalize(city, Normalizer.Form.NFKD).replaceAll("[^\\p{ASCII}-_ ]", "")
.replace(' ', '_');
List<String> possibleTimeZones = availableTimeZones.stream().filter(zid -> zid.endsWith("/" + cityName))
.collect(Collectors.toList());
Rough way I found to achieve is -
private static String getSourceLocalTimeZone(String countryCode, String city, String sourceLocalTimeZone) {
String[] timeZones = com.ibm.icu.util.TimeZone.getAvailableIDs(countryCode);
for (String timeZone : timeZones) {
String cityFromTimeZone = null;
String[] value = timeZone.split("/");
if (value != null && value.length > 0) {
cityFromTimeZone = value[value.length - 1].replace("_", " ");
}
if (city!=null && city.matches("(.*)" + cityFromTimeZone + "(.*)")) {
sourceLocalTimeZone = timeZone;
break;
}
}
if (sourceLocalTimeZone == null || (sourceLocalTimeZone.isEmpty())) {
if(timeZones.length>0)
sourceLocalTimeZone = timeZones[0];
}
return sourceLocalTimeZone;
}
Dependency -
<dependency>
<groupId>com.ibm.icu</groupId>
<artifactId>icu4j</artifactId>
<version>4.6</version>
</dependency>

How can I get originalvalues from EF savechanges for complex primary key

i've implemented audit log for all my db objects but i'm running into an issue because of a special case.
i have code similar to the following.
private IEnumerable<CDMA_CHANGE_LOG> GetAuditRecords(DbEntityEntry ent, string userId, string primaryKeyId, bool updateFlag, object originalEntity)
{
List<CDMA_CHANGE_LOG> result = new List<CDMA_CHANGE_LOG>();
string changeId = Guid.NewGuid().ToString();
TableAttribute tableAttr = ent.Entity.GetType().GetCustomAttributes(typeof(TableAttribute), false).SingleOrDefault() as TableAttribute;
// Get table name (if it has a Table attribute, use that, otherwise get the pluralized name)
string entityName = tableAttr != null ? tableAttr.Name : ObjectContext.GetObjectType(ent.Entity.GetType()).Name;
var changeTime = DateTime.UtcNow;
if (ent.State == EntityState.Modified)
{
foreach (var prop in ent.OriginalValues.PropertyNames)
{
//we cant use this because getdatabasevalues will error out when there are 2 rows in the db for this primarykey (U,A)
//var originalValue = ent.GetDatabaseValues().GetValue<object>(prop) == null ? "" : ent.GetDatabaseValues().GetValue<object>(prop).ToString();
var currentValue = ent.CurrentValues[prop] == null ? "" : ent.CurrentValues[prop].ToString();
// For updates, we only want to capture the columns that actually changed
var primaryKey = GetPrimaryKeyValue(ent);
if (originalValue != currentValue)
{
result.Add(new CDMA_CHANGE_LOG()
{
USERID = userId,
DATECHANGED = changeTime,
EVENTTYPE = "M", // Modified
ENTITYNAME = entityName,
PRIMARYKEYVALUE = primaryKey.ToString(),
PROPERTYNAME = prop,
OLDVALUE = originalValue,
NEWVALUE = currentValue,
CHANGEID = changeId
});
}
}
}
return result;
}
now i cant use originalValue = ent.GetDatabaseValues().GetValue(prop) because the records have two fields as primary key so i thought i could query the original value manually from the controller and send as
object originalEntity
so i'm wondering how do i cast originalEntity to the current entity
var originalValues = Convert.ChangeType(originalEntity, ent.GetType());
does not work.
i'm finding it difficult to get. thanks for any help.

Error in date validation MVC

My model class property looks like this
public DateTime PurchaseDate { get; set; }
and inside view
#Html.TextBoxFor(model => model.PurchaseDate, new { #class = "form-control date-picker" })
#Html.ValidationMessageFor(model => model.PurchaseDate)
and I am giving a date like this in form
19/06/2015
But it gives validation message and not allows page to be submitted, message is like this
The field PurchaseDate must be a date.
if I give date in mm/dd/yyyy format it works. Can anyone point out what I am doing wrong here?
The client side error is occurring because by default jquery.validate tests the value using the MM/dd/yyyy format. You can override the $.validator.addMethod('date', function (value, element) function to test that the value is in the dd/MM/yyyy you expect. Note the following code is from my own jquery plugin associated with a #Html.DatePickerFor() helper method which renders a data-dateformat attribute in the output based on the servers culture, so it may be an overkill for your needs
Add the following scripts (not in document.ready, but after jquery.validate.unobtrusive)
Date.prototype.isValid = function () {
return !isNaN(this.getTime());
}
globalDate = function (value, formatString) {
// Initialise a new date
var date = new Date(0);
if (value == undefined) {
// Return todays date
return date;
}
// Get the components of the format
// The separator can be forward slash, hyphen, dot and/or space
var regex = new RegExp(/([dMy]+)([\s/.-]+)([dMy]+)([\s/.-]+)([dMy]+)/);
//var format = regex.exec(this.inputFormat);
var format = regex.exec(formatString);
// Get the components of the value
regex = new RegExp(/(\d+)([\s/.-]+)(\d+)([\s/.-]+)(\d+)/);
value = regex.exec(value);
// Check the value is valid
if (value === null || value[2] !== format[2] || value[4] !== format[4]) {
// Its not valid
date.setTime(Number.NaN);
return date;
}
// TODO: What if year entered as 2 digits?
var day = Number.NaN;
var month = Number.NaN;
var year = Number.NAN;
if (format[1].charAt(0) === 'd') {
// little-endian (day, month, year)
day = parseInt(value[1]);
month = parseInt(value[3]) - 1;
year = parseInt(value[5]);
} else if (format[1].charAt(0) === 'M') {
// middle-endian (month, day, year)
day = parseInt(value[3]);
month = parseInt(value[1]) - 1;
year = parseInt(value[5]);
} else {
// big endian (year, month, day)
day = parseInt(value[5]);
month = parseInt(value[3]) - 1;
year = parseInt(value[1]);
}
date.setFullYear(year);
date.setMonth(month);
date.setDate(day);
// Check its valid
if (date.getDate() !== day || date.getMonth() !== month || date.getFullYear() !== year) {
date.setTime(Number.NaN);
return date;
}
return date;
}
$.validator.addMethod('date', function (value, element) {
var format = "dd/MM/yyyy";
return this.optional(element) || globalDate(value, format).isValid();
}
If you only ever want to test for the format dd/MM/yyyy, then you could simplify the globalDate() function by just using
var date = new Date();
date.setHours(0, 0, 0, 0);
var components = value.split('/');
var day = components[0];
var month = components[1];
var year = components[2];
date.setFullYear(year);
....
Edit
Further to OP's comments regarding server side validation failing, the server culture needs to accept a date string in the format dd/MM/yyyy. In the web.config.cs file
<system.web>
<globalization culture="en-AU" uiCulture="en-AU"/> // adjust to your culture code
....
If you want to explicitly set the expected date format for your model property then you can do this using the DisplayAttribute
[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}", ApplyFormatInEditMode = true)]
public DateTime PurchaseDate { get; set; }
Otherwise, the current culture of the server would be used (which in your case happens to be MM/dd/yyyy).
It appears that in order for client-side validation to respect the DataFormatString we need to use EditorFor in place of TextBoxFor
#Html.EditorFor(model => model.PurchaseDate, new { #class = "form-control date-picker" })
#Html.ValidationMessageFor(model => model.PurchaseDate)

Entity Framework 4: Generic return type for a method

I have the following code -
var db = new DBEntities();
var entity = //get entity;
lblName.Text = string.Empty;
var names = entity.Names.OrderBy(x => x.Value).ToList();
for (var i = 0; i < names .Count; i++)
{
if (i == names .Count - 1) lblName.Text += names [i].Value + ".";
else lblName.Text += names [i].Value + ", ";
}
I'll have several For loops like above which will format the value to be displayed in a label. I'm trying to make a method out of it which will do the formatting when I pass in the collection and the label, something like -
void FormatValue(List<??> items, Label label)
{
//For loop
//Format value
}
What do I pass in for the List. How do I make this generic enough so I'll be able to use it for all entity.Names, entity.Xxx, entity.Yyy etc?
Make the method itself generic and allow the caller to specify a formatter:
void FormatValue<T>(List<T> items, Label label, Func<string, T> formatter)
{
foreach(var item in items)
{
label.Text += formatter(item);
}
}
You can then call the method like:
FormatValue<Name>(entity.Names.OrderBy(x => x.Value).ToList(),
lblName,
i => i.Value + ", ");

How to do If statement in Linq Query

I currently have a list that contains the following
CountryCode (string)
CountryStr (string)
RegionStr (string)
RegionID (int)
AreaStr (string)
AreaID (int)
This is a flattened set of linked data (so basically the results of a joined search that ive stored)
The MVC route will only pass one string which I then need to match up to the data at the right level in the heirachy.
So I'm trying to query the CountryStr then if it doesn't produce results the region then the area; but I need to do that bit of the query and for instance...
var datURL = (from xs in myList
//query 1
where xs.RegionStr == rarREF
select new
{
regionID = xs.RegionId,
CountryID = xs.CountryCd
}
//IF theres no results
where xs.AreaStr == rarREF
select new
{
AreaID = xs.AreaID
regionID = xs.RegionId,
CountryID = xs.CountryCd
}
).ToList();
The only way I see of doing this at the moment is running each query separately then checking which returned values and using that one. I'm hoping there's a cleverer, cleaner method.
It won't be very easy to read, but you could do this in a single pass using something like this:
var datURL = (from xs in myList
where xs.RegionStr == rarREF || xs.AreaStr == rarREF
select new
{
AreaID = (xs.AreaStr == rarRef ? xs.AreaID : default(int)),
RegionID = xs.RegionId,
CountryID = xs.CountryId
}
).ToList();
It might also be easier to read the query if it's rewritten slightly:
var datURL = (from xs in myList
let isArea = xs.AreaStr == rarREF
let isRegion = xs.RegionStr == rarREF
where isRegion || isArea
select new
{
AreaID = (isArea ? (int?)xs.AreaID : null),
RegionID = xs.RegionId,
CountryID = xs.CountryId
}
).ToList();
If we save the comparison result, we can reuse it later. I also added a cast to int? to show how you could use a nullable value instead of using 0 as your "no Area" value.
Aren't you looking for or operator? Does this not generate the results you want?
var datURL = (from xs in myList
where xs.RegionStr == rarREF || xs.AreaStr == rarREF
select new
{
AreaID = xs.AreaStr == rarREF ? xs.AreaID : default(int)
regionID = xs.RegionId,
CountryID = xs.CountryCd
}).ToList();

Resources