Thursday 1 August 2019

Consistency guard of enums used in Entity Framework

This is a consistency guard for enums in Entity Framework. It is a mechanism for protecting an entity in Entity Framework or just EF, in case an enum value was loaded from the database with an illeagal value. An illeagal enum value would be any value of an enum that cannot be parsed into an enum. We use Enum.IsDefined method (at first running Convert.ChangeType) to check if the value for the enum is leagal or not. We define a helper class BrokenEnumValue to contain our metadata about enum values that are illeagal or 'broken'. The rest of the code in this article goes into the DbContext class (Or ObjectContext would also work) that EF uses. The ObjectMaterialized event is added in the constructor for example.
            var objectContext = ((IObjectContextAdapter) this).ObjectContext;
            _log = (ILog) AutofacHostFactory.Container.Resolve(typeof(ILog));
            objectContext.ObjectMaterialized += ObjectContext_ObjectMaterialized;
Our helper POCO:

    public class BrokenEnumValue
    {
        public string PropertyName { get; set; }
        public string PropertyTypeName { get; set; }
        public Guid? SchemaGuid { get; set; }
        public string OldValue { get; set; }
        public string CorrectedValue { get; set; }

        public override string ToString()
        {
            return $"{PropertyName} {PropertyTypeName} {SchemaGuid} {OldValue} {CorrectedValue}";
        }
    }



        private void ObjectContext_ObjectMaterialized(object sender, ObjectMaterializedEventArgs e)
        {
            var brokenEnumProperties = FixBrokenEnumProperties(e.Entity);
            if (brokenEnumProperties.Any())
            {
                Type objType = e.Entity.GetType();
                var idProperty = objType.GetProperty("Id");
                Guid? schemaGuid = idProperty?.GetValue(e.Entity, null) as Guid?;
                foreach (var brokenEnum in brokenEnumProperties)
                    brokenEnum.SchemaGuid = schemaGuid;
                string brokenEnumsInfo = string.Join(" ", brokenEnumProperties.Select(b => b.ToString()).ToArray());
                _log.WriteWarning($"Detected broken enum propert(ies) in entity and resolved them to default value if available in enum (None): {brokenEnumsInfo}");
            }
        }

           public IList<BrokenEnumValue> FixBrokenEnumProperties(object obj)
        {
            var list = new List<BrokenEnumValue>();
            try
            {
                if (obj == null) return list;
       
                PropertyInfo[] properties = obj.GetType().GetProperties();
                foreach (PropertyInfo property in properties)
                {
                    if (property.GetIndexParameters()?.Any() == true)
                        continue; //skip indexer properties
                    if (property.PropertyType.IsArray)
                    {
                        Array a = (Array) property.GetValue(obj);
                        for (int i = 0; i < a.Length; i++)
                        {
                            object o = a.GetValue(i);
                            list.AddRange(FixBrokenEnumProperties(o));
                        }
                        continue; //continue to next iteration
                    }
                    object propValue = property.GetValue(obj, null);
                    var elems = propValue as IList;
                    if (elems != null)
                    {
                        foreach (var item in elems)
                        {
                            list.AddRange(FixBrokenEnumProperties(item));
                        }
                    }
                    else
                    {
                        if (property.PropertyType.IsEnum && !IsEnumDefined(propValue, property.PropertyType))
                        {
                            var correctedValue = GetDefaultEnumValue(propValue, property.PropertyType);
                            list.Add(new BrokenEnumValue
                            {
                                CorrectedValue = correctedValue?.ToString(),
                                OldValue = propValue?.ToString(),
                                PropertyName = property.Name,
                                PropertyTypeName = property.PropertyType.FullName,
                            });
                            property.SetValue(obj, correctedValue);
                            
                        }
                        if (property.PropertyType.IsClass && (property.PropertyType.GetCustomAttributes(typeof(DataContractAttribute))?.Any() == true)
                                                          && !(property.PropertyType == typeof(string)) && !property.PropertyType.IsValueType)
                        {
                            list.AddRange(FixBrokenEnumProperties(propValue));
                        }
                    }
                }
            }
            catch (Exception err)
            {
                _somelog.WriteError($"Expection occurred trying to fix broken enum properties: {err}");
            }
            return list;
        }

           private static T GetDefaultEnumValue<T>(T entity, Type propertyType)
           {
               foreach (var enumValue in propertyType.GetEnumValues())
               {
                   if (String.Compare(enumValue.ToString(), "None", StringComparison.OrdinalIgnoreCase) == 0)
                   {
                       return (T)enumValue;
                   }
               }
               return entity;
           }

        private static bool IsEnumDefined(object entity, Type propertyType)
        {
            var castedValue = Convert.ChangeType(entity, propertyType);
            return Enum.IsDefined(propertyType, castedValue);
        }


With this guard, we can avoid that the entity does not load in case an illeagal value was loaded for a given enum. Note that our fallback is looking for the enum value mapping to the [None] enum member, so we fallback to the [None] enum value, if it exists. Mosts enum should have a [None] member mapping to the enum integer value 0. You can of course adjust the strategy used here. I believe such a consistency guard would be helpful for many applications using EF.

Wednesday 31 July 2019

Slide-in animation of forms in Angular 8

This is a cool feature in Angular 8, the support of animations. First off, we define our animation in a Typescript file like for example a sliding in translation of the X-axis. app-animation.ts
import { trigger, animate, transition, style, group, query } from '@angular/animations';

export const slideInAnimation = trigger('slideInAnimation', [
  // Transition between any two states
  transition('* <=> *', [
    // Events to apply
    // Defined style and animation function to apply
    // Config object with optional set to true to handle when element not yet added to the DOM
    query(':enter, :leave', style({ position: 'fixed', width: '100%', zIndex: 2 }), { optional: true }),
    // group block executes in parallel
    group([
      query(':enter', [
        style({ transform: 'translateX(100%)' }),
        animate('0.5s ease-out', style({ transform: 'translateX(0%)' }))
      ], { optional: true }),
      query(':leave', [
        style({ transform: 'translateX(0%)' }),
        animate('0.5s ease-out', style({ transform: 'translateX(-100%)' }))
      ], { optional: true })
    ])
  ])
]);


In our app.component.ts, we import this animation slideInAnimation like this:
import { slideInAnimation } from './app.animation';
@Component({
  selector: 'pm-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  animations: [ slideInAnimation ]
})
export class AppComponent {
  pageTitle = 'SomeAcme Product Management';


Note the association of the slideInAnimation above. And in the app component template (app.component.html) we just declare a template variable for the router outlet and set it up to trigger on route activation.
<div class="container" [@slideInAnimation]="o.isActivated ? o.activatedRoute: ''">
  <router-outlet #o="outlet"></router-outlet>
</div>
This video shows the effect. Note that this is an excerpt from Deborah Kurata's course on Angular routing from Pluralsight. Video of this technique:

Sunday 28 July 2019

Angular 8 - Datepicker and timepicker

I have been looking at the ngx-bootstrap components for Angular lately. These components are very nice and similar to the components that Bootstrap provides, for Angular. I have tested it out with Angular 8. First off, we import the datepicker and time picker of this library like this: ng add ngx-bootstrap --components datepicker ng add ngx-bootstrap --components timepicker Inside your app.module.ts, you should see the two components imported:
import { BsDatepickerModule } from "ngx-bootstrap/datepicker";
import { TimepickerModule } from 'ngx-bootstrap/timepicker';

It is also included to the imports. My sample projects app.module.ts looks like this:
import { BrowserModule } from "@angular/platform-browser";
import { FormsModule } from "@angular/forms";
import { NgModule } from "@angular/core";
import { HttpClientModule } from "@angular/common/http";
import { AppComponent } from "./app.component";
import { UserSettingsFormComponent } from "./user-settings-form/user-settings-form.component";
import { AppFocusDirective } from "./data/app-focus.directive";
import { ButtonsModule } from "ngx-bootstrap/buttons";
import { BsDatepickerModule } from "ngx-bootstrap/datepicker";
import { BsLocaleService } from 'ngx-bootstrap/datepicker';
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { defineLocale } from "ngx-bootstrap/chronos";
import { nbLocale } from "ngx-bootstrap/locale";
import { TimepickerModule, TimepickerConfig } from 'ngx-bootstrap/timepicker';
import { getTimepickerConfig  } from './TimepickerConfig';

defineLocale("nb", nbLocale);

@NgModule({
  declarations: [AppComponent, UserSettingsFormComponent, AppFocusDirective],
  imports: [
    BrowserModule,
    FormsModule,
    HttpClientModule,
    ButtonsModule.forRoot(),
    BsDatepickerModule.forRoot(),
    BrowserAnimationsModule,
    TimepickerModule.forRoot()
  ],
  providers: [ BsLocaleService,
   { provide: TimepickerConfig, useFactory: getTimepickerConfig }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {

  constructor(private bsLocaleService: BsLocaleService) {
    this.bsLocaleService.use('nb');
  }
}

Observe the localization adjustments I have included here, for Norwegian localization of the date picker. I also include a custom factory function for setting up the time picker to not show the meridian button, i.e. show a 24 hours format of the time.
import { TimepickerConfig } from "ngx-bootstrap/timepicker";

// such override allows to keep some initial values

export function getTimepickerConfig(): TimepickerConfig {
  return Object.assign(new TimepickerConfig(), {
    hourStep: 2,
    minuteStep: 5,
    showMeridian: false,
    readonlyInput: false,
    mousewheel: true,
    showMinutes: true,
    showSeconds: false
  });
}


If you want to test out the adjustments I made, the Github repo is available here: https://github.com/toreaurstadboss/Angular8FormsNgxBootstrapTesting