Sunday 16 September 2018

Getting groups (including nested) for AD user - AccountManagement vs DirectorySearcher vs WindowsIdentity


Testing to see what is fastest to get all groups (i.e. include nested groups, not just top level groups) => WindowsIdentity appears to easily be the fastest, followed by directorysearcher and then accountManagement.

Over 50 tests:
WindowsIdentity average of .3 seconds
DirectorySearcher average of  1.2 seconds
AccountManagement average of 2.3 seconds


Suspect there is a reason why not to use WindowsIdentity because it is so much faster, but not sure what its restrictions are yet.



    class Program
    {
        static AccountManagementService ams = new AccountManagementService();
        static DirectorySearcherService dss = new DirectorySearcherService();
        static WindowsIdentityService wis = new WindowsIdentityService();

        static void Main(string[] args)
        {
            var userId = ConfigurationManager.AppSettings["UsernameToFind"];
            var iterations = 50;
            var amsTimeTaken = new List<long>();
            var dssTimeTaken = new List<long>();
            var wisTimeTaken = new List<long>();

            for (var i = 0; i < iterations; i++)
            {
                amsTimeTaken.Add(CallAms(userId));
                dssTimeTaken.Add(CallDss(userId));
                wisTimeTaken.Add(CallWis(userId));
                Console.WriteLine($"iteration {i} done");
            }

            Console.WriteLine($"AMS average is {(amsTimeTaken.Sum() / iterations)}");
            Console.WriteLine($"DSS average is {(dssTimeTaken.Sum() / iterations)}");
            Console.WriteLine($"WIS average is {(wisTimeTaken.Sum() / iterations)}");

            Console.ReadKey();
        }

        static long CallAms(string userId)
        {
            var watch = System.Diagnostics.Stopwatch.StartNew();
            ams.Lookup(userId);
            watch.Stop();
            return watch.ElapsedMilliseconds;
        }
        static long CallDss(string userId)
        {
            var watch = System.Diagnostics.Stopwatch.StartNew();
            dss.Lookup(userId);
            watch.Stop();
            return watch.ElapsedMilliseconds;
        }
        static long CallWis(string userId)
        {
            var watch = System.Diagnostics.Stopwatch.StartNew();
            wis.Lookup(userId);
            watch.Stop();
            return watch.ElapsedMilliseconds;
        }
    }


    public class WindowsIdentityService
    {
        public List<string> Lookup(string userName)
        {
            List<string> result = new List<string>();
            WindowsIdentity wi = new WindowsIdentity(userName);

            foreach (IdentityReference group in wi.Groups)
            {
                try
                {
                    result.Add(group.Translate(typeof(NTAccount)).ToString().Replace("HIQ\\",""));
                }
                catch (Exception ex) { }
            }
            return result;
        }
    }

    public class AccountManagementService
    {
        string _domain = ConfigurationManager.AppSettings["AmsDomain"];
        public List<string> Lookup(string userId)
        {
            var output = new List<string>();

            using (var ctx = new PrincipalContext(ContextType.Domain, _domain))
            {
                using (var user = UserPrincipal.FindByIdentity(ctx, userId))
                {
                    if (user != null)
                    {
                        output = user.GetAuthorizationGroups() //this returns a collection of principal objects
                            .Select(x => x.SamAccountName)
                            .ToList();
                    }
                }
            }
            return output;
        }
    }


    public class DirectorySearcherService
    {

        public List<string> Lookup(string userId)
        {
            List<string> userNestedMembership = new List<string>();
           
            DirectoryEntry domainConnection = new DirectoryEntry();
            domainConnection.Path = ConfigurationManager.AppSettings["DssDomainPath"];

            DirectorySearcher samSearcher = new DirectorySearcher();
           
            samSearcher.SearchRoot = domainConnection;
            samSearcher.Filter = "(samAccountName=" + userId + ")";
            samSearcher.PropertiesToLoad.Add("displayName");

            SearchResult samResult = samSearcher.FindOne();

            if (samResult != null)
            {
                DirectoryEntry theUser = samResult.GetDirectoryEntry();
                theUser.RefreshCache(new string[] { "tokenGroups" });

                foreach (byte[] resultBytes in theUser.Properties["tokenGroups"])
                {
                    System.Security.Principal.SecurityIdentifier mySID = new System.Security.Principal.SecurityIdentifier(resultBytes, 0);

                    DirectorySearcher sidSearcher = new DirectorySearcher();

                    sidSearcher.SearchRoot = domainConnection;
                    sidSearcher.Filter = "(objectSid=" + mySID.Value + ")";
                    sidSearcher.PropertiesToLoad.Add("distinguishedName");
                    sidSearcher.PropertiesToLoad.Add("name");

                    SearchResult sidResult = sidSearcher.FindOne();

                    if (sidResult != null)
                    {
                        userNestedMembership.Add((string)sidResult.Properties["name"][0]);
                    }
                }
               
            }
            else
            {
                Console.WriteLine("The user doesn't exist");
            }

            return userNestedMembership;

        }

    }

Tuesday 14 August 2018

checking if an element contains overflow & appling a style


elements that need to be checked to see if they have any overflow have the .mark-if-overflow class applied to them.

function CheckAllCellsForOverflow() {
    $(".mark-if-overflow .cell-wrapper").each(function (index) {
        if (($(this).prop('scrollWidth') > $(this).width()) || ($(this).prop('scrollHeight') > $(this).height())) {
            $(this).addClass('contains-hidden-overflow');
        } else {
            $(this).removeClass('contains-hidden-overflow');
        }
    });
}

filtering & sorting json in .net

these guys:
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

via the Newtonsoft.Json nuget package.


Entity in database describing a filter:

    public partial class DisplayGroupDataFilter
    {
        public int Id { get; set; }
        public int GroupId { get; set; }
        public string FilterRoot { get; set; }
        public string PropertyNameToFilterOn { get; set; }
        public string FilterItemValues { get; set; }
        public string FilterType { get; set; }
        public bool Active { get; set; }
   
        public virtual DisplayGroup DisplayGroup { get; set; }
    }

Entity in database describing a sort:

    public partial class DisplayGroupDataSort
    {
        public int Id { get; set; }
        public int GroupId { get; set; }
        public string Field { get; set; }
        public string Direction { get; set; }
        public bool Active { get; set; }
        public int Order { get; set; }
   
        public virtual DisplayGroup DisplayGroup { get; set; }
    }


the DataService:


        /// <summary>
        /// applies filters and sort to supplied json data
        /// </summary>
        /// <param name="data">the json data</param>
        /// <param name="dataFilters">collection of filters to apply to data</param>
        /// <param name="dataSorts">collection of sorts to apply to data</param>
        /// <returns></returns>
        private string ApplManipulationsToGroupData(string data, List<DisplayGroupDataFilter> dataFilters, List<DisplayGroupDataSort> dataSorts)
        {
            JObject jdata = JObject.Parse(data);
            foreach (var filter in dataFilters)
            {
                jdata = ApplyDataFilterToGroupData(jdata, filter);
            }
            if (dataSorts.Any())
            {
                ApplySortToGroupData(jdata, dataSorts);
            }
            return JsonConvert.SerializeObject(jdata);
        }


        private JObject ApplySortToGroupData(JObject data, List<DisplayGroupDataSort> dataSorts)
        {
            // get the collection we want to sort our of the data
            JArray existing = (JArray)data.SelectToken("Location.Rows");
           
            // create a custom comparer that understands how we want sort elements
            var comparer = new JObjectSortComparer(dataSorts);

            // sort the collection using our custom comparer
            var sorted = existing.AsQueryable().OrderBy(obj => (JObject)obj, comparer);
           
            // put the sorted collection back into the orginal data
            data["Location"]["Rows"] = new JArray(sorted);
           
            return data;
        }

        private JObject ApplyDataFilterToGroupData(JObject data, DisplayGroupDataFilter dataFilter)
        {
            JObject jobject = data;

            string filterRoot = dataFilter.FilterRoot; // the node we want to apply the filter at e.g. "Location.Rows";
            string tokenNameToCheck = dataFilter.PropertyNameToFilterOn; // the name of property we want to filter on e.g. "BedId";
            string filterType = dataFilter.FilterType; // the type of filter e.g. "Exclude" or "Include";
            List<string> filterItems = dataFilter.FilterItemValues.Split(',').ToList();  // new List<string> { "K2", "K3" };
            List<JToken> matchedItems = new List<JToken>(); // will contain a list of items that match our filter

            // get the collection we want to apply the filter too
            var rows = jobject.SelectToken(filterRoot);
            // find and make a collection of the items that match our filter
            foreach (var item in rows)
            {
                var value = (string)item.SelectToken(tokenNameToCheck);
                if (filterItems.Contains(value))
                {
                    matchedItems.Add(item);
                }
            }

            // remove any of our matched items
            if (filterType == "Exclude")
            {
                foreach (var item in matchedItems)
                {
                    item.Remove();
                }
            }
            // remove anything not in our matched items (i.e. only keep matches)
            if (filterType == "Include")
            {
                var nonMatches = rows.Where(i => !matchedItems.Contains(i)).ToList();
                foreach (var item in nonMatches)
                {
                    item.Remove();
                }
            }

            return jobject;
        }


The custom comparer that does the actual sorting:

    /// <summary>
    /// compares two jobjects based on a list of sorts (containing a field name and sort direction) passed into the constructor
    /// </summary>
    public class JObjectSortComparer : IComparer<JObject>
    {
        private List<DisplayGroupDataSort> requiredSorts;

        public JObjectSortComparer(List<DisplayGroupDataSort> sorts)
        {
            requiredSorts = sorts;
        }

        public int Compare(JObject a, JObject b)
        {
            return DoCompare(a, b, 0);
        }

        private int DoCompare(JObject a, JObject b, int depth)
        {
            var fieldName = requiredSorts[depth].Field;

            var compareResult = string.Compare((string)a.SelectToken(fieldName), (string)b.SelectToken(fieldName));
            if (requiredSorts[depth].Direction.ToLower() == "desc")
            {
                compareResult = compareResult * -1;
            }

            if (compareResult == 0 && depth < requiredSorts.Count - 1)
            {
                return DoCompare(a, b, depth + 1);
            }

            return compareResult;
        }
    }



Tuesday 22 May 2018

basic application monitor


apparently monitoring is important.

As a developer I like to reinvent the wheel so here's the basic application monitor.

A windows service runs a heap of monitors based on 3 types; website monitor checks for a good http response code, db monitor runs a small select query, healthchecker requests some json from the app (this is a custom thing we do on the apps so the app can report how healthy it is). The service then outputs it all into a big json file on the filesystem. The website then reads the json and displays it.

There's no notifications, there's no history, there's no interface to add more monitors (you have to add via json configuration). The goal is to keep it as simple as possible and be a snapshot of what is happening right now.









garfield & 404 pages

have I put these here already? who knows. Dear Garfield boss, please don't sue me.



nz geohunter - game based on googlemaps api

A game based on googlemaps api.



The difficulty increases when you get three right in a row and decreases when you get one wrong. Harder = smaller population centers, easier = cities.

google maps - how to get co-ordinates for drawing buildings

This will let you draw shapes on the satellite picture and then output the lat lng coordinates of each point of the line. Those co-ordinates can then be saved and used by something else to draw shapes on maps.

This needs the drawing library! Make sure it is on the end of the link to the google maps api e.g. <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=drawing">

Open up the dev tools and after drawing a rectangle, set of lines or a point it should output the co-ordinates to the console.

<body>
    <div id="map"></div>
    <script>

        function initMap() {
            var uluru = {
                lat: -39.072474,
                lng: 174.055992
            };
            var map = new google.maps.Map(document.getElementById('map'), {
                center: uluru,
                zoom: 17,
                mapTypeId: 'satellite'
            });
           
            // this creates the base options for the drawings (polygons/buildings)
            var drawingManager = new google.maps.drawing.DrawingManager({
                drawingMode: google.maps.drawing.OverlayType.MARKER,
                drawingControl: true,
                drawingControlOptions: {
                    position: google.maps.ControlPosition.TOP_CENTER,
                    drawingModes: ['marker', 'circle', 'polygon', 'polyline', 'rectangle']
                },
                markerOptions: {
                    icon: 'https://developers.google.com/maps/documentation/javascript/examples/full/images/beachflag.png'
                },
                rectangleOptions: {
                    fillOpacity: 0
                },
                circleOptions: {
                    //fillColor: '#ffff00',
                    fillOpacity: 1,
                    strokeWeight: 5,
                    clickable: false,
                    editable: true,
                    zIndex: 1
                }
            });
            drawingManager.setMap(map);

            // this just outputs the coordinates once a polygon is drawn on the screen
            google.maps.event.addListener(drawingManager, 'overlaycomplete', function (event) {
                console.log(event.type);
                if (event.type == 'rectangle') {
                    var ne = event.overlay.getBounds().getNorthEast();
                    var sw = event.overlay.getBounds().getSouthWest();

                    var bounds = "ne: " + ne.lat() + ", sw: " + sw.lat() + ", ne: " + ne.lng() + ", sw: " + sw.lng();
                    console.log(bounds);
                }
                if (event.type == 'polyline') {
                    //var radius = event.overlay.getRadius();
                    var coordinatesArray = event.overlay.getPath().getArray();
                    var allCoordinates = "";
                    for (var i = 0; i < coordinatesArray.length; i++) {
                        //console.log("new google.maps.LatLng(" + coordinatesArray[i].lat() + ", " + coordinatesArray[i].lng() + "),");
                        var coord = "{lat: " + coordinatesArray[i].lat() + ", lng: " + coordinatesArray[i].lng() + "},";
                        allCoordinates = allCoordinates + coord;
                    }
                    console.log(allCoordinates);
                }
                if (event.type == 'marker') {
                    var lat = event.overlay.getPosition().lat();
                    var lng = event.overlay.getPosition().lng();
                    var coord = "{lat: " + lat + ", lng: " + lng + "},";
                    console.log(coord);
                }
               
            });
        }
    </script>
    <script src="https://maps.googleapis.com/maps/api/js?key=[your key here]&libraries=drawing&callback=initMap" async defer></script>
</body>

wow. After you've got the coords and drawn some polygons based off them you can edit the lines by setting editable to true and adding a listener to the path you've created and output the new co-ordinates. This allows for fine tuning of the points of the polygon.

                // this is just used when editing the buildings - will output the new paths
                buildingDefinition.getPath().addListener('set_at', function(index, event) {
                    var coordinatesArray = this.getArray();
                    var allCoordinates = "";
                    for (var i = 0; i < coordinatesArray.length; i++) {
                        var coord = "{lat: " + coordinatesArray[i].lat() + ", lng: " + coordinatesArray[i].lng() + "},";
                        allCoordinates = allCoordinates + coord;
                    }
                    console.log(allCoordinates);
                });



HTML map of the hospital, using Google maps. This has a custom overlay (background) which shows the roads internal to the hospital. Different types of parking is hightlighed and bus stops and entrances to the hospital are shown. A list of services can be clicked on which will highlight the building they are in and center the map on that location.



Clicking a building will show details:




Doing whatever:



'Ambulance house' and 'Helicopter house' may or may not be the offical names of the buildings.

Building co-ordinates are stored in this format in sepearte js files:
var buildings = [];
var ict = {
    title: "ICT Services",
    key: "itServices",
    titleCoordinates: { lat: -39.0747770079001, lng: 174.0569919347763 },
    description: "Applications, Business Intellignence, Reporting and Development",
    coordinates: [{
        lat: -39.07470621091739,
        lng: 174.05701607465744
    }, {
        lat: -39.07475202073784,
        lng: 174.0570965409279
    }, {
        lat: -39.074962329077316,
        lng: 174.05688732862473
    }, {
        lat: -39.07491027261602,
        lng: 174.0568122267723
    }]
}

buildings.push(ict);
etc - add more buildings as required

This mob of co-ordinates is just referenced in the HTML
    <script type="text/javascript" src="buildingPersistance.js"></script>
    <script type="text/javascript" src="otherPersistance.js"></script>

Ok.
You have to sign up with google api and get a map key. Cram the script tag in your html:

    <script src="https://maps.googleapis.com/maps/api/js?key=[your key here]"></script>

Hook up an initialisation method to run when the maps stuff is loaded

        google.maps.event.addDomListener(window, 'load', initMap);

You need an initialisation method

        function initMap() {
            var uluru = {
                lat: -39.072474,
                lng: 174.055992
            };
            map = new google.maps.Map(document.getElementById('map'), {
                center: uluru,
                zoom: 17,
                styles: [{
                    featureType: 'poi',
                    stylers: [{
                        visibility: 'off'
                    }]
                }, {
                    featureType: 'transit',
                    elementType: 'labels.icon',
                    stylers: [{
                        visibility: 'off'
                    }]
                }]
            });

            // draw any buildings we have
            drawBuildings(map, buildings);
etc ...

When creating the map object the center will set the start location of the map
Styles defines what objects you want to show on the map (a heap of stuff shows by default)

Draw the things you want

        function drawBuildings(map, buildings) {
            for (var i = 0; i < buildings.length; i++) {
                var building = buildings[i];
                var buildingCoords = [];
                for (var c = 0; c < building.coordinates.length; c++) {
                    var coordinate = building.coordinates[c];
                    buildingCoords.push(new google.maps.LatLng(coordinate.lat, coordinate.lng));
                }
                var strokeColor = building.strokeColor ? building.strokeColor : '#000';
                var fillColor = building.fillColor ? building.fillColor : '#000';
                var buildingDefinition = new google.maps.Polygon({
                    paths: buildingCoords,
                    draggable: false,
                    editable: false,
                    strokeColor: strokeColor,
                    strokeOpacity: 0.8,
                    strokeWeight: 2,
                    fillColor: fillColor,
                    fillOpacity: 0.35,
                    originalStrokeColor: strokeColor,
                    originalFillColor: fillColor,
                    key: building.key,
                    description: building.description,
                    title: building.title
                });
                buildingDefinition.setMap(map);


Adding a popup for a building

                // build the popup information if we have a description for this building
                if (buildingDefinition.description) {
                    buildingDefinition.addListener('click', function(event) {
                        var contentString = '<div class="info-list-title">' + this.title + "</div>" + this.description;
                        infowindow.setContent(contentString);
                        infowindow.setPosition(event.latLng);
                        infowindow.open(map);
                    });
                }

Creating a label for a building (using maplabel-compiled.js from ... somewhere?)

                // create a label for this building
                if (building.title && building.titleCoordinates) {
                    var mapLabel = new MapLabel({
                        text: building.title,
                        position: new google.maps.LatLng(building.titleCoordinates.lat, building.titleCoordinates.lng),
                        map: map,
                        fontSize: 9
                    });
                    mapLabels.push(mapLabel);
                }







Async example


Here's a clue. The parent method needs the async keyword, the methods that are running asynchronously (called from the parent) don't. Weird.


A method that runs a heap of methods asynchronously. This guy needs the async keyword in the method signature:


        public async Task<List<RunResult>> RunMonitors(List<IMonitor> monitors)
        {
            var results = new List<RunResult>();
            try
            {
                var tasks = new List<Task<RunResult>>();
                foreach (var monitor in monitors)
                {
                    _logger.Debug($"About to run monitor {monitor.Name()}");

                    var newTask = Task.Run(() => monitor.Run());

                    tasks.Add(newTask);
                }

                _logger.Debug($"About to wait for monitor to complete; started {tasks.Count} tasks");
                await Task.WhenAll(tasks);

                _logger.Debug($"Monitors have completed; going to collect results");
                foreach (var task in tasks)
                {
                    results.Add(task.Result);
                }
            } catch (Exception ex)
            {
                _logger.Error($"Error while running monitors: {ex}");
            }
            return results;
        }

This bit starts the tasks running:
                foreach (var monitor in monitors)
                {
                    _logger.Debug($"About to run monitor {monitor.Name()}");

                    var newTask = Task.Run(() => monitor.Run());

                    tasks.Add(newTask);
                }

This bit causes everything to wait until all the tasks have completed:
                await Task.WhenAll(tasks);

This bit gets the results from each of the tasks
                _logger.Debug($"Monitors have completed; going to collect results");
                foreach (var task in tasks)
                {
                    results.Add(task.Result);
                }



Example of monitor run method with return type of Task<[an object]>. This guy doesn't need the async keyword (even tho it is running asynchronously with all its buddies).

        public Task<RunResult> Run()
        {
            DateTime started = DateTime.Now;
            var result = new RunResult
            {
                MonitorType = _type,
                Name = _name,
                RequestedAt = started
            };
            try
            {
                HttpResponseMessage response = _client.GetAsync(_url).Result;

                string responseString = response.Content.ReadAsStringAsync().Result;
                result.RunStatus = response.IsSuccessStatusCode ? Lookups.StatusLookup.Success : Lookups.StatusLookup.Failure;
                result.Response = response.IsSuccessStatusCode ? null : responseString;
                _logger.Info($"WebSiteMonitor: {_name} got a IsSuccessStatusCode of {response.IsSuccessStatusCode} and response of: {result.Response}");

            } catch(Exception ex)
            {
                _logger.Error($"WebSiteMonitor: {_name} caused an exception of {ex}");
                result.RunStatus = Lookups.StatusLookup.Failure;
                result.Response = ex.Message;
            }
            result.CompletedAt = DateTime.Now;
            return Task.FromResult(result);
        }


Example of how the parent method is called (just normal):

                var monitors = monitorManager.GetMonitors(config);
                var results = engine.RunMonitors(monitors).Result; // this method has the async keyword
                monitorManager.SaveRunResults(results);

Wednesday 9 May 2018

Adding FormsAuthentication to validate against AD to a MVC website with no auth


You could use the new identity stuff. lol.



Update web.config to say it's formsauth, stick a path to an action that will deal with the login

  <system.web>
    <authentication mode="Forms">
      <forms loginUrl="~/Account/LogOn"/>
    </authentication>
    <authorization>
      <deny users="?" />
    </authorization>

Create a controller for the above path

    public class AccountController : Controller
    {
        public ActionResult LogOn()
        {
            return View();
        }

Create a corresponding view and a model to hold logon details

viewmodel:

    public class LogOnModel
    {
        [Required]
        [Display(Name = "User name")]
        public string UserName { get; set; }

        [Required]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }

    }

view:

@model Mixr.Web.Models.LogOnModel
@{
    ViewBag.Title = "Log On";
}


<div class="logonPage">
    <div class="logonContainer">
        <h2>Log On</h2>
        <p>

        </p>

        <div class="logonForm">
            @Html.ValidationSummary(true, "Login was unsuccessful. Please correct the errors and try again.")

            <form action="~/Account/LogOn" method="post">
                <fieldset>
                    <legend>Please enter your user name and password:</legend>

                    <div class="labelAndField">
                        <div class="editor-label">
                            @Html.LabelFor(m => m.UserName)
                        </div>
                        <div class="editor-field">
                            @Html.TextBoxFor(m => m.UserName)
                            @Html.ValidationMessageFor(m => m.UserName)
                        </div>
                    </div>
                    <div class="labelAndField">
                        <div class="editor-label">
                            @Html.LabelFor(m => m.Password)
                        </div>
                        <div class="editor-field">
                            @Html.PasswordFor(m => m.Password)
                            @Html.ValidationMessageFor(m => m.Password)
                        </div>
                    </div>

                    <input class="logonButton" type="submit" value="Log On" />
                </fieldset>
            </form>

        </div>
    </div>
</div>


go back to your controller and create a method to recieve the post:

        [HttpPost]
        public ActionResult LogOn(LogOnModel model)
        {
            if (ModelState.IsValid)
            {
                if (Membership.ValidateUser(model.UserName, model.Password))
                {
                    FormsAuthentication.SetAuthCookie(model.UserName, false);
                    return RedirectToAction("Index", "Home");
                }
                ModelState.AddModelError("", "The user name or password provided is incorrect.");
            }

            return View(model);
        }

wow. great. How does this know where your AD is?
Go to the web.config and in system.web add a AD membership provider:

    <membership defaultProvider="AspNetActiveDirectoryMembershipProvider">
      <providers>
        <clear />
        <add name="AspNetActiveDirectoryMembershipProvider" connectionStringName="ADService" type="System.Web.Security.ActiveDirectoryMembershipProvider,System.Web, Version=4.0.0.0, Culture=neutral,PublicKeyToken=b03f5f7f11d50a3a" attributeMapUsername="sAMAccountName" />
      </providers>
    </membership>


The above expects an entry in your connectionStrings section:

  <connectionStrings>
        <add name="ADService" connectionString="LDAP://wowgreat.co.nz:389" />
  </connectionStrings>


if you're feeling fancy give the user some way to logout

        public ActionResult LogOff()
        {
            FormsAuthentication.SignOut();

            return RedirectToAction("Index", "Home");
        }

Great. now they're authenticated. now you have to do some authorization.

Wednesday 14 March 2018

installing services



install service:

sc create "[Service name]" binpath="[Path to exe]"

e.g.
sc create "MCP Extractor Service PAS DEV" binpath="C:\Midland Clinical Portal\Services\PAS Extractor\DocumentExtractor.Pas.WindowsService.exe"


to update the description:

sc description "[Service name]" "Midland Clinical Portal Document Extractor Service"

Monday 26 February 2018

WebStock 2018 notes


Andy Budd - The accidental leader (high performaing teams)

* processes don't make good products, people do
* build the team people want to be on
* create the best environment to do great work
* schedule time to experiment and innovate
* execute at pace
* remove organisational barriers
* remove the trash from jobs
* Google Aristotle Project
* psychological safety; and other stuff
how this applies to us
* execute at pace -> reducing long lead times/WIP -> org barriers/trash
* are we high performing team? where can we be better

Scott Hansleman - Open source pancreas

* a lot of engineers building the same systems - v complicated
* social meda to share, building on other parts; building the pancreas
* extracting data from closed source apps
how this applies to us
* how to extract data to send to GPs (results?), how to insert data from GPs (referrals?)
* who owns the data? it's about the patients but they can't access it. What apps can access.

Haley Van Dyck - USDS

* healthcare fail -> US Digital Service
* 86B on IT projects, 94% failure (inc over budget/late), 40% never released
* focused teams helping depts
* only appointee to stay in admin change O->T
1. never let a crisis go to waste
2. turn risk into asset
take other peoples' risk
3. design for failure
have something/incremental steps
4. mind the culture change trap
deliver results to change culture; don't try to change people
how this applies to us
* unfortunately we are probably one of the orgs they would be helping
* use some of their techniques? small, focused teams?

Lee Vinsel - The innovation fetish
(historian)

* innovation speak vs actual innovation
* little evidence of disruptive innovation (but amazon or netflix)
* 67% of software effort is in maintenance / not design (or innovation)
* failure to consider maintenance when building & maintenance jobs have lower status
how this applies to us
* more effort to prevent maintenance tasks?
* retire old stuff? - pas integrations?
* get our ALM happening?

using online tests to help with the diagnosis of ADHD


The application consists of a collection of tests written in html/javascript to collect data on things like attention span, impulse resistance, planning etc that can help in a diagnosis for ADHD. The front end is a .net MVC app with results presented via SQL Server Reporting Services (and linked to from the app). The tests were all designed by one of the awesome doctors here. He even coded a lot of them. Maybe I can retire soon. The system went live at the start of 2018.

Goals:

  • allow testing for ADHD without having to have a psychologist in the room (spend their time elsewhere)
  • faster testing than paper versions
  • establish a set of norms for NZ - we currently don't have any so have to compare our kids to others e.g. US kids
  • store the results electronically, once we have norms can show side-by-side with a result set to compare


Example test 1: Response inhibition (not clicking on things you shouldn't). When a 'P' shows and you should click the RESPONSE button. When an 'R' shows you shouldn't click the response button. The Ps show a lot so you get in a pattern of clicking & preventing yourself from clicking on the Rs can be challenging.



results for this show how often the client avoided clicking the R and also the speed of the responses  with a trend line to show if it got better or worse over time



Example test 2: Tower of London

Moving stacks of objects around to match a given pattern with a limited amount of move available. Tests planning before jumping in an clicking everything. There are three sets of each difficulty (i.e. 3 tests where there are 3 moves to complete, 3 tests where there are 4 moves etc)



Results show how long it took for the first click (how much planning you did) to happen and also the successes.


Example test 3: Recalling a sequence of digits. The sequence gets longer as long as the client is getting the values correct.


Example results for coding (a symbol is displayed, the client has to map that to a letter and then click the correct button). Shows how many mappings were done correctly, how many wrong, and associated trend lines (did it get better or worse over time)







play sound through javascript - using flash over html

CITRX! Why do you play choppy sound sometimes when using IE/html5? Bad citrix.

SoundManager can play sounds over flash; if no flash then it will fallback to using html5.
http://www.schillmania.com/projects/soundmanager2/

Put all the sound manager support stuff somewhere in your project



Reference soundmanager2.js from your HTML


Setup the Sound Manager; put the path to where you put the support stuff:

        $(document).ready(function() {
            soundManager.setup({
                // where to find flash audio SWFs, as needed
                url: '@Url.Content("~/Content/swf/")',
                preferFlash: true,
                onready: function() {
                    // SM2 is ready to play audio!
                }
            });
        });


Use the Sound Manager to play a sound

    var mySound = soundManager.createSound({
        url: baseUrl + "Content/Voice/Hello.mp3"
    });

    // ...and play it
    mySound.play();




Citrix!

javascript benchmarker

how do you know if your javascript is being impacted by something on the client? CPU, memory, disk io. I don't know. Maybe set a timeout for a second and then when the timeout is triggered see how close to a second it was? The more variance from a second the more impact something is having on the browser/js. Then just keep looping that timeout.

benchmarker is probably the wrong term, but life is hard.

Seems mostly the thing that effects JS is CPU. To test:
https://blogs.msdn.microsoft.com/vijaysk/2012/10/26/tools-to-simulate-cpu-memory-disk-load/
http://download.sysinternals.com/files/CPUSTRES.zip


function Benchmarker() {

    var times = []; // records the time at each interval check
    var interval = 1000; // the amount of time (in msec) to wait between time checking
    var warningVarianceThreshold = 100; // the amount of time (in msec) as a variance from the expected time that counts as a warning
    var alertVarianceThreshold = 150; // the amount of time (in msec) as a variance from the expected time that counts as an alert
    var warningCount = 0;
    var alertCount = 0;
    var stopFlag = false;

    var intervalCallback = null;    // a callback function called each time the interval occurs

    // this method will run at a given interval and record the time
    // will compare the current time to the last run time and establish a variance to the expected difference
    function checkInterval() {
        if (stopFlag) {
            return;
        }
        var d = new Date();
        var currentSeconds = d.getTime();
        if (times.length > 0) {
            var lastSeconds = times[times.length - 1];
            var timeChanged = currentSeconds - lastSeconds;
            //console.log(currentSeconds + "; " + timeChanged);
            var variance = Math.abs(timeChanged - interval);
            if (variance >= alertVarianceThreshold) {
                alertCount++;
            } else if (variance >= warningVarianceThreshold) {
                warningCount++;
            }
            if (typeof intervalCallback === "function") {
                var data = getData();
                data = $.extend(data, { lastVariance: variance });
                intervalCallback(data);
            }
        } else {
            //console.log(currentSeconds);
        }
        times.push(currentSeconds);
        setTimeout(function () { checkInterval(); }, interval);
    }

    this.getData = function() {
        var data = { interval: interval, intervalCount:times.length, warningVarianceThreshold: warningVarianceThreshold, alertVarianceThreshold: alertVarianceThreshold, warningCount: warningCount, alertCount: alertCount };
        return data;
    }

    this.start = function () {
        checkInterval();
    }
    this.stop = function () {
        stopFlag = true;
    }

    this.getAlertCount = function () {
        return alertCount;
    }
    this.getWarningCount = function () {
        return warningCount;
    }
    this.setIntervalCallback = function(callback) {
        intervalCallback = callback;
    }
}

Collect client information via javascript


function ClientEnvironmentData() {

    var data = [];
    data.push({ Key: "navigator.appName", Value: navigator.appName });
    data.push({ Key: "navigator.userAgent", Value: navigator.userAgent });
    data.push({ Key: "navigator.appVersion", Value: navigator.appVersion });
    data.push({ Key: "navigator.appCodeName", Value: navigator.appCodeName });
    data.push({ Key: "navigator.platform", Value: navigator.platform });
    data.push({ Key: "navigator.oscpu", Value: navigator.oscpu });
    data.push({ Key: "navigator.cookieEnabled", Value: navigator.cookieEnabled });
    data.push({ Key: "navigator.doNotTrack", Value: navigator.doNotTrack });
    data.push({ Key: "navigator.language", Value: navigator.language });
    data.push({ Key: "navigator.onLine", Value: navigator.onLine });
    data.push({ Key: "navigator.product", Value: navigator.product });
    data.push({ Key: "navigator.productSub", Value: navigator.productSub });
    data.push({ Key: "navigator.vendor", Value: navigator.vendor });
    data.push({ Key: "navigator.vendorSub", Value: navigator.vendorSub });

    data.push({ Key: "window.outerWidth", Value: window.outerWidth });
    data.push({ Key: "window.outerHeight", Value: window.outerHeight });
    data.push({ Key: "window.innerWidth", Value: window.innerWidth });
    data.push({ Key: "window.innerHeight", Value: window.innerHeight });

    this.getData = function() {
        return data;
    }

    this.getDataAsXml = function() {
        var xml = "";
        for (var i = 0; i < data.length; i++) {
            xml += "<" + data[i].Key + ">" + data[i].Value + "</" + data[i].Key + ">";
        }
        return xml;
    }
}

Tuesday 6 February 2018

Make REST service



Controllers inherit from ApiController


Methods return an IHttpActionResult


Use the helper stuff to return good or bad results


Creating a Web API project will include MVC stuff, but you don't need it. Can create an Empty project and just add in Web API. Include the MVC stuff to get views etc. You can include if you want to make a nice homepage for your REST service with some cat pictures on it?

.Net consume REST using HttpClient

Create HttpClient and setup in the constructor


Make requests as required. Use .Result after the post to make life synchronous.


Pull info from the response if needed, use Newtonsoft.Json to deserialize to objects.


Stuff, stuff, stuff



    public class AwesomeService : IAwesomeService
    {
        private static HttpClient _client;

        public AwesomeService()
        {
            if (_client == null)
            {
                // trust all certificates
                System.Net.ServicePointManager.ServerCertificateValidationCallback =
                    ((sender, certificate, chain, sslPolicyErrors) => true);
               
                _client = new HttpClient();
                var baseUrl = ConfigurationManager.AppSettings["ServiceBaseUrl"];
                _client.BaseAddress = new Uri(baseUrl);
                _client.DefaultRequestHeaders.Accept.Clear();
                _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            }
        }

        public void DoThing(string from, string to)
        {
            var paramTemplate = ConfigurationManager.AppSettings["ParamTemplate"] ?? "v1.0/email?from={1}&to={2}";
            var path = string.Format(paramTemplate, from, to);
            HttpResponseMessage response = _client.PostAsync(path, null).Result;
            response.EnsureSuccessStatusCode();
        }
}


        public string GetFullname(string userName)
        {
            var pathTemplate = ConfigurationManager.AppSettings["ActiveDirectoryUserTemplate"] ?? "v1.0/user?userId={0}";
            var path = string.Format(pathTemplate, userName);
            string fullName = null;
            HttpResponseMessage response = _client.GetAsync(path).Result;
            if (response.IsSuccessStatusCode)
            {
                var json = response.Content.ReadAsStringAsync().Result;
                fullName = JsonConvert.DeserializeObject<string>(json);
            }

            return fullName;
        }

Configuring Log4net


reference:
https://stackoverflow.com/questions/10204171/configure-log4net-in-web-application


Add the nuget package:



Stick this line somewhere:

[assembly: log4net.Config.XmlConfigurator(Watch = true)]

(like the AssemblyInfo.cs file, or just cram it at the top of your controller like a madman - but only include it once)

Configure web.config settings:

  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
  </configSections>
  <log4net debug="true">
    <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
      <file value="RollingLog.txt" />
      <appendToFile value="true" />
      <rollingStyle value="Size" />
      <maxSizeRollBackups value="5" />
      <maximumFileSize value="10MB" />
      <staticLogFileName value="true" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%-5p %d %5rms %-22.22c{1} %-18.18M - %m%n" />
      </layout>
    </appender>
    <root>
      <level value="DEBUG" />
      <appender-ref ref="RollingLogFileAppender" />
    </root>
  </log4net>

Get a reference to the logger

private static readonly ILog Log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

Go crazy logging things




Output looks like


Versioning in REST

Details at
https://github.com/Microsoft/aspnet-api-versioning/wiki

Add the nuget package


Decorate the controller














Access the API using the new path (described in the values that decorate the controller), e.g. http://whatever/api/v1.0/email

Can test using Swagger. Install Swashbuckle nuget package. Run your project and go to your URL with swagger on the end, e.g. 
http://localhost:10539/swagger
which will redirect to http://localhost:10539/swagger/ui/index
This lists your controllers.

Click the one you want to test and available actions are shown. The non-versioned one will not work. 


Click the one with the version in the URL. Enter params where required and click 'try it out!'.

Confirm the response is ok.



Edit:

The WebApiConfig should be updated automatiically(?) but sometimes isn't?

The error:
The inline constraint resolver of type 'DefaultInlineConstraintResolver' was unable to resolve the following inline constraint: 'apiVersion'

Mean you need to resolve the apiVersion value in WebApiConfig; it should look something like

            // Web API routes
            var constraintResolver = new DefaultInlineConstraintResolver()
            {
                ConstraintMap =
                {
                    ["apiVersion"] = typeof( ApiVersionRouteConstraint )
                }
            };
            config.MapHttpAttributeRoutes(constraintResolver);
            config.AddApiVersioning();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            ); 

Oracle.ManagedDataAccess - accessing Oracle from .Net without having to install any Oracle client


"ODP.NET, Managed Driver is a 100% native .NET code driver. No additional Oracle Client software is required to be installed to connect to Oracle Database."

Add this nuget package:




Stuff gets added to config; configure connection

Have had a problem when multiple connection strings with them not reading from the aliased data source. Can skip the datasource config elements and put it directly into the connection string.

Accessing the connection string as per standard



Use the OracleCommand same way as SqlCommand. To issue command (e.g. update):

or to read data (e.g. select):


Parameters in the query are prefixed with colon:


NOTE: Ensure that the params added in the code (e.g. using the command.Parameters.Add) are done in the same order as they occur in the query. Even though a name is specified when adding a parameter, by default that is ignored.