The Standards

Table of content

Overview

Why Code Review?

Having code reviews as part of our development workflow will bring different benefits to the entire team, such as:

  1. Fewer bugs. Code reviews will decrease the amount of bugs that make it to production.
  2. Better security. Code reviews make it easy to spot potential vulnerabilities and fix them before they make their way to production servers.
  3. Better performance. Code reviews help spot performance issues and regressions.
  4. Code quality. Things like readability, efficiency, and maintainability of your code may not always directly affect the end users, but they are tremendously important in the long run.
  5. Knowledge sharing. Everyone on our team can learn from each other by reviewing each other’s code.

General

The following list of items indicate coding standards independent of the technology used.

C#

C#

Table of content

C#

General

Developers guide to debugging

(click on the image below)


Developers Guide to Debugging

public IEnumerable<exSample> GetSamples(SignifyHRDAL dbContext)
{
  var samples = SampleHelper.GetSampleList();

  if (samples != null && samples.Count > 0)
  {
    //Do something
  }

  return samples;
}
public void UpdateSample()
{
  //Bad
  bool isActive = false;
  //Good
  var isActive = false;
  
  //Bad
  string sampleDescription = "This is the new description";
  //Good
  var sampleDescription = "This is the new description";
}
public class Cat
{
    // Auto-implemented properties.
    public int Age { get; set; }
    public string Name { get; set; }

    public Cat()
    {
    }

    public Cat(string name)
    {
        this.Name = name;
    }
}
C#

Statements, expressions and operators

References

 

Additional

C#

Separation of concerns

The main concept here is Single Responsibility - "a class/method must have only one reason to change".  This deals specifically with cohesion.

//Bad
public exSample UpdateSample(int id, string newDescription, int productId)
{
    //Method purpose is to return a sample
    var sample = SampleHelper.GetSample(id);

    //sample description is updated
    sample.description = newDescription;
  
  	//A new product is also added the sample and the VAT calculated
  	var product = ProductHelper.GetProduct(productId);
    product.VAT = product.TotalAmount * 14 / 100;
  	sample.Products.Add(product);

    return samples;
}

//Good
//Method to update the sample (serves one purpose)
public exSample UpdateSample(int id, string newDescription, int productId)
{
    //Method purpose is to return a sample
    var sample = SampleHelper.GetSample(id);

    sample.description = newDescription;

    return samples;
}

//Method to add a product to the sample and do the product calculations where necessary (serves one purpose)
private exSample AddSampleProduct(exSample sample, int productId)
{
    //A new product is also added the sample and the VAT calculated
  	var product = ProductHelper.GetProduct(productId);
    product.VAT = product.TotalAmount * 14 / 100;
  	sample.Products.Add(product);
  
  return sample;
}

https://docs.microsoft.com/en-us/dotnet/architecture/modern-web-apps-azure/architectural-principles#single-responsibility

C#

Types

References

Build in reference types

Nullable reference types

SignifyTypeExtensions

 

TODO When are Types used? How are they used? Converting to other Types?

C#

Classes and Structs

C#

Interfaces

References

C#

Comments

References

Commenting Conventions

 

 

 

C#

Arrays

References

C#

Generics

References

C#

Strings

References

 

Date Formatting (V8)

Ensure where ever a date is displayed to the user that it is done according to system setup / user preference:

public string CurrentDate
{
    get
    {
        return DateTime.Now.ToDateTime_Format();
    }
}

TODO : ToSafeString?

Any other extensions we can use/expand on

 

C#

Namespaces

Usage

Make use of using directives to enable improved readability and limit coding effort.

// Not making use of using directive
namespace MyTestProgram
{
    public class MyTestClass
    {
        private void DoSomething()
        {
            if(!System.IO.Directory.Exists("C:\\TestFolder\\");
                System.IO.Directory.CreateDirectory("C:\\TestFolder\\");
     
            var files = System.IO.Directory.GetFiles("C:\\MainFolder\\", "*.txt");
            var fileNames = new List<string>();
       
            foreach (var file in files)
            {
                fileNames.Add(System.IO.Path.GetFileName(file));
            }
        }
    }
}
            
// Making use of using directive
using System.IO;               
namespace MyTestProgram
{
    public class MyTestClass
    {
        private void DoSomething()
        {
            if(!Directory.Exists("C:\\TestFolder\\");
                Directory.CreateDirectory("C:\\TestFolder\\");
     
            var files = Directory.GetFiles("C:\\MainFolder\\", "*.txt");
            var fileNames = new List<string>();
       
            foreach (var file in files)
            {
                fileNames.Add(Path.GetFileName(file));
            }
        }
    }
}               

 

Aliasing

Use aliasing to prevent ambiguous references if different namespaces have objects with the same name.

using DataDomain = SignifyHR.Data.Domain;
using Domain = SignifyHR.Domain;
using SignifyHR.Helpers;

namespace SignifyHR.Learning
{
    public class HaveFun
    {
        public bool IsFun(int activityId)
        {
            var activity = Domain.Activity.TryFetch(activityId);
            var rules = DataDomain.Activity.SelectActivityPathwayRuleItemsList(new SessionHelper(), activityId);
            
            return activity != null && rules != null;
        }
    }
}

 

 

C#

Exception handling

References

Creating and Throwing Exceptions

 

Catch

Ensure the error is logged in a catch.
Actions returning a page, view or partial should return the appropriate error page.
Return the correct notification to user if elements are changed during execution or for AJAX/JSON responses.

// Log error
// Return correct error page/partial
public PartialViewResult PersonalDetails(int discussionId)
{
    try
    {
        return PartialView("_DiscussionPersonalDetails", new DiscussionPersonalDetailsViewModel(SessionHandler, discussionId));
    }
    catch (Exception ex)
    {
        ErrorUtilities.LogException(ex);
        return PartialView("_Error");
    }
}


// Log error
// Return correct error notification
[HttpPost]
public ActionResult Approve(int id, DiscussionSection activeTab)
{
    try
    {
        ApproveDiscussion(id, DiscussionStatus.COMPLETED);
        return Json(new { success = true, message = "Successfully approved.", redirectUrl = Url.EncryptedAction("Index", new { id, activeTab }) });
    }
    catch (Exception ex)
    {
        ErrorUtilities.LogException(ex);
        return Json(new { success = false, message = "There was a problem saving this section." });
    }
}


// Log error
// Return correct error notification
if (itemDisplaySetting == ItemDisplaySetting.Hide)
    HidePathwayStep(ref e);
else
{
    try
    {
        var step = pwStep.Fetch(sessionHandler, Int32.Parse(hfObjectID.Value));

    }
    catch (Exception ex)
    {
        ErrorUtilities.LogException(ex);
        lblReferenceDescription.Text = string.Format("{0}{1}", " Message: ", ex.Message);
    }
}

 

When to catch

Applying exceptions are usually done for the following cases:

Shared libraries

Shared libraries

SignifyTypeExtensions

var dateVariable = dt.ToDateTime(0, "ExpiredBeforeDate"); // extends DataTable type
var boolVariable = dt.ToBoolean(0, "ViewAssessmentTranscript");
var stringVariable = dt.ToString(0, "DocumentReferenceNumber");
var decimalVariable = this.ToDecimal();
var intVariable = this.ToInt32();
var doubleVariable = this.ToDouble();
var floatVariable = this.ToFloat();

TODO: Add example method for each type

Shared libraries

SignifyControlLibrary

<asp:Repeater>
<asp:HiddenField>
<asp:TextBox>
<asp:Literal>
<asp:DropDownList>
<!--
AllowPaging - switch paging on or off for repeater
PageSize
        public enum PageSizes
        {
            Ten = 10,
            Twenty = 20,
            Fifty = 50,
            Hundred = 100
        }
NoResultsFoundMessage - override no results message
-->
<signify:SignifyRepeater ID="rptOverlapHistories" runat="server" AllowPaging="false" NoResultsFoundMessage="No results found for your search criteria.">
<!--
Visible is by default false
-->
<signify:SignifyHiddenField ID="hfProcessObjectID" runat="server" Value="0" /> 
<!--
DisplayType
        public enum DisplayTypes
        {
            TextBox,
            TextArea,
            SearchBox
        }
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            switch (_displayType)
            {
                case DisplayTypes.TextBox:
                    this.TextMode = TextBoxMode.SingleLine;

                    if (this.MaxLength == 0)
                        this.MaxLength = 500;
                    break;
                case DisplayTypes.TextArea:
                    this.TextMode = TextBoxMode.MultiLine;

                    if (this.MaxLength == 0)
                        this.MaxLength = 2000;
                    break;
                case DisplayTypes.SearchBox:
                    this.TextMode = TextBoxMode.SingleLine;

                    if (this.MaxLength == 0)
                        this.MaxLength = 200;
                    break;
                default:
                    break;
            }
        }
-->
<signify:SignifyTextBox MaxLength="50" ID="txtEmpNo" runat="server" DisplayType="SearchBox" Width="170px"></cc:SignifyTextBox>
<!--
FormatDate - true/false (Should the text be Formatted to yyyy/mm/dd before rendering to the client?)
UseSession_DateFormat
EncodeHTML - true/false (Should the text be HtmlEncoded before rendering to the client?)
-->
<signify:SignifyLiteral ID="lPreferred" runat="server" Text="Preferred"></signify:SignifyLiteral>
<!--
Text - places i image before text in page heading
-->
<signify:SignifyPageDescription ID="pdOptMsg" runat="server" Text="When selected, steps in the pathway are copied but empty.<br>Each step must be configured e.g. for the Document step a document needs to be uploaded.<br>When not selected, a copy of the pathway structure is also made e.g. for a Document step, the document will already be available for download"></signify:SignifyPageDescription>
<!--
AddAllOption - Add an extra option named \"All\" to drop down list and returns a \"%\" in the value property, this properties can be changed
AllOptionText - Sets the text property for the \"All\" option
AllOptionValue - Sets the value property for the \"All\" option"
-->
<signify:SignifyDropDownList ID="ddlAppType" runat="server" AddAllOption="true" AllOptionText="" AllOptionValue="" DataValueField="ObjectID" />

 

TODO: Single example per control, state what the defaults are

 

Shared libraries

SignifyHR.Helpers

Helpers available:

TODO - description of helper and signature of 3 methods

Shared libraries

Third-Party Integrations

SigniFlowUtilities

AdobeUtilities

QuestionMark

Rustici

ZoomConnect

Sharepoint

MVC

MVC

Table of contents

1. General

2. Naming Conventions

3. Models / Domain Models

4. View Models

5. View

6. Controllers

7. Routing

8. Attributes

9. Extensions and Tools

MVC

General

For a general guide on ASP.NET MVC, click on the following imageimage-1602129428352.png

image-1601320574138.png

TODO : what happens in web.config? Add Example zip folder to General page

https://signature.signifyhr.co.za/books/tools/page/v8---mvc-development

MVC

Naming Conventions

The following naming convention must be followed when creating any of the MVC pattern sections. All names must be created using Pascal-Case.

Model

Controller

Attributes

All custom filter attributes must be named according to its function e.g. BypassSessionAuthorisation

View

e.g. for the HomeController

image-1602128806399.png

View Models

Routing 

The registrations file for a areas route must be named {AreaName}AreaRegistration e.g LearningStoreRegistration.cs

 

 

MVC

Model/ Domain Model

Go To Naming Conventions

For information regarding models, click the images below

image-1602130700401.png

MVC

ViewModel

Go To Naming Conventions

For information regarding view models, click the images below

image-1602130609554.png

TODO : properties defined and properties inherited, private set properties, constructors

Example PathwayViewModel

MVC

View

Go To Naming Conventions

For information regarding razor views, click the images below

image-1602130864155.png

Responsible for presenting viewmodels, viewbags and other data structures in the format of the end user requirements

Reusing Views

Layouts

Common presentational areas (e.g. page header, footer) can be put in a layout view.

Partial Views

Views without a layout that reuse fragments of presentational code e.g. used in modals.

Helpers Classes

In views we often need some code snippets to do tiny tasks such as formatting data or generating HTML tags. 

The method in the helper class

image-1602126711528.png

The implementation of the class  and method in the view

image-1602126828227.png

 

TODO: add examples from v8 mvc page

Ensure list data bound during post (use for instead of for each)

Difference between Html.Form and Ajax.Form

Use of hidden field variables and where they should be placed in a view

MVC

Controller

Go To Naming Conventions

For information regarding Controllers, click the images below

image-1602130775935.png

 

namespace SignifyHR.Areas.Portal.Controllers
{
    public class HomeController : BaseController
    {
        [UserTranslationFilter]
        public ActionResult Index(PortalTabs? activeTab)
        {
            var model = new HomeViewModel(SessionHandler, activeTab)
            {
                Title = TranslationResources.PageHeadingsLmsPortal,
            };

            return View(model);
        }
 }

https://signature.signifyhr.co.za/books/tools/page/v8---mvc-development/edit

When to use ActionResult and JSONResult

AllowGet

Follow C# standards

MVC

Routing

Go To Naming Conventions

For a guide regarding routing, click on the image below

image-1602129606089.png

Area specific routing is implemented in the Area Registration cs file and can have custom routing as required by the area e.g. LearningStoreAreaRegistration.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace SignifyHR.Areas.LearningStore
{
    public class LearningStorereaRegistration : AreaRegistration
    {
        public override string AreaName
        {
            get
            {
                return "LearningStore";
            }
        }

        public override void RegisterArea(AreaRegistrationContext context)
        {
            context.MapRoute(
             name: "LearningStore.Pages",
             url: "LearningStore/{id}/{slug}",
             defaults: new { controller = "Page", action = "Index", slug = UrlParameter.Optional },
             constraints: new { id = @"\d+" },
             namespaces: new[] { "SignifyHR.Areas.LearningStore.Controllers" }
            );
            
            context.MapRoute(
             name: "LearningStore.DEFAULT",
             url: "LearningStore/{controller}/{action}",
             defaults: new { controller = "Home", action = "Index" },
             namespaces: new[] { "SignifyHR.Areas.LearningStore.Controllers" }
            );

        }
    }
}
MVC

Attributes

Go To Naming Conventions

The ASP.NET MVC framework supports four different types of filters executed in the following order:

  1. Authorisation filters – Implements the IAuthorizationFilter attribute.
  2. Action filters – Implements the IActionFilter attribute.
  3. Result filters – Implements the IResultFilter attribute.
  4. Exception filters – Implements the IExceptionFilter attribute.

The common authorisation filters that is used in Signify are

image-1601322053620.png

Example of use

Applying a filter on a single method in the controller

image-1602127617546.png

Applying a filter on the controller as a whole

image-1602127647291.png

MVC

Extensions and Tools

Logging

Using NLog

public class HomeViewModel : PortalMasterModel
{
    private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
    private static readonly Logger _lmsLogger = LogManager.GetLogger("LMS");
 
    public HomeViewModel()
    {
        // Using the class name as the name of the logger
        _logger.Info("Initializing HomeViewModel");
 
        // Using a new logger instance with a property automatically added
        _lmsLogger.WithProperty("ActiveTab", activeTab).Info("Initializing HomeViewModel");
 
        // Setting a property permanently on a logger
        _logger.SetProperty("Key", "Value");
        _logger.Info("Testing EventProperties");
 
        // Adding properties automatically in the message
        _logger.Info("Testing dynamic EventProperty Key={PropertyName}", "PropertyValue");
 
        // Logging an exception
        _logger.WarnException("Exception", new System.Exception("Example Exception"));
    }
}
Example of log levels
Level Message Logger Properties Callsite Exception
Info Initializing HomeViewModel SignifyHR.Areas.Portal.ViewModels.HomeViewModel   SignifyHR.Areas.Portal.ViewModels.HomeViewModel..ctor  
Info Initializing HomeViewModel LMS ActiveTab=TrainingRequirements SignifyHR.Areas.Portal.ViewModels.HomeViewModel..ctor  
Info Testing EventProperties SignifyHR.Areas.Portal.ViewModels.HomeViewModel Key=Value SignifyHR.Areas.Portal.ViewModels.HomeViewModel..ctor  
Info Testing dynamic EventProperty Key="PropertyValue" SignifyHR.Areas.Portal.ViewModels.HomeViewModel PropertyName=PropertyValue|Key=Value SignifyHR.Areas.Portal.ViewModels.HomeViewModel..ctor  
Warn Exception SignifyHR.Areas.Portal.ViewModels.HomeViewModel Key=Value SignifyHR.Areas.Portal.ViewModels.HomeViewModel..ctor System.Exception: Example Exception
Log Levels
Level Typical Use
Fatal Something bad happened; application is going down
Error Something failed; application may or may not continue
Warn Something unexpected; application will continue
Info Normal behavior like mail sent, user updated profile etc.
Debug For debugging; executed query, user authenticated, session expired
Trace For trace debugging; begin method X, end method X
Rules

A rule is a logger element with the following attributes:

NLog.config
!--
    Create a rule to only log entries from the LMS HomeViewModel logger,
    using the logDatabase target, and applicable on log levels Info and above
-->
<logger name="SignifyHR.Areas.Portal.ViewModels.HomeViewModel" minlevel="Info" writeTo="logDatabase" enabled="true" final="true" />

 

MVC

API Conventions

Propagate errors. If an error occurred, return a 500. The status code should tell you what happened, not the result of the call itself. Follow the DefaultApiConventions, which state that the action should return void and produce a 200, 404, or 400 status. In MVC these rules don't have to be followed as strictly, but with the APIs it is non-negotiable for standardisation.

[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesDefaultResponseType]
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)]
public static void Delete(
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)]
[ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)]
object id) { } 

https://github.com/dotnet/aspnetcore/blob/master/src/Mvc/Mvc.Core/src/DefaultApiConventions.cs

 

Entity Framework

Entity Framework

Table of contents

Entity Framework

General

using SignifyHR.Core;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
 
namespace SignifyHR.Data.Domain
{
    public partial class exSample : IAuditable
    {
        protected static IQueryable<exSample> ValidSamples(SignifyHRDAL dbContext, EagerLoadParameters eagerLoadParms = null)
        {
            var samples = dbContext.exSamples.AsQueryable();
 
            if (eagerLoadParms != null)
            {
                if (eagerLoadParms.IncludeSampleDocuments)
                    samples = samples.Include(item => item.exSampleDocuments);
 
                if (eagerLoadParms.IncludeSampleComments)
                    samples = samples.Include(item => item .exSampleComments);
            }
 
            return samples;
        }
 
        public static IEnumerable<exSample> FetchAll(ISessionHandler sessionHandler, SearchParameters searchParms, EagerLoadParameters eagerLoadParms = null)
        {
            using (var dbContext = new SignifyHRDAL(sessionHandler))
            {
                var results = ValidSamples(dbContext, searchParms, eagerLoadParms);
                                 
                return results.OrderByDescending(item => item.Id)
                        .Skip(searchParms.Skip)
                        .Take(searchParms.Take)
                        .ToList();               
            }
        }
    }
}
using SignifyHR.Core;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
 
namespace SignifyHR.Data.Domain
{
    public partial class exSample : IAuditable
    {
        #region Eager Load Parameters
 
        public class EagerLoadParameters : BaseSearchParameters
        {
            public bool IncludeSampleDocuments { get; set; }
            public bool IncludeSampleComments { get; set; }
        }
 
        #endregion
 
        #region Protected Methods
 
        protected static IQueryable<exSample> ValidSamples(SignifyHRDAL dbContext, EagerLoadParameters eagerLoadParms = null)
        {
            var samples = dbContext.exSamples.AsQueryable();
 
            if (eagerLoadParms != null)
            {
                if (eagerLoadParms.IncludeSampleDocuments)
                    samples = samples.Include(item => item.exSampleDocuments);
 
                if (eagerLoadParms.IncludeSampleComments)
                    samples = samples.Include(item => item .exSampleComments);
            }
 
            return samples;
        }
    }
}
protected static IQueryable<exSample> ValidSamples(SignifyHRDAL dbContext, EagerLoadParameters eagerLoadParms = null)
protected static IQueryable<exSample> FilterSamples(SignifyHRDAL dbContext, SearchParameters searchParms, EagerLoadParameters eagerLoadParms = null)

public static exSample Fetch(ISessionHandler sessionHandler, int id, EagerLoadParameters eagerLoadParms = null)
public static exSample TryFetch(ISessionHandler sessionHandler, int id, EagerLoadParameters eagerLoadParms = null)

 

using SignifyHR.Core;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
 
namespace SignifyHR.Data.Domain
{
    public partial class exSample : IAuditable
    {
		public class POCOPreview
        {
            public int ExampleId { get; set; }
            public string ExampleDescription { get; set; }
            public bool ExampleBool { get; set; }
        }
    }
}

 

using SignifyHR.Core;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
 
namespace SignifyHR.Data.Domain
{
    public partial class exSample : IAuditable
    {
        #region Search Parameters
 
        public class SearchParameters : BaseSearchParameters
        {
            public int? SomeId { get; set; }
            public string Description { get; set; }
            public bool IsUsed { get; set; }
        }
 
        #endregion
 
        #region Protected Methods
 
        protected static IQueryable<exSample> FilterSamples(SignifyHRDAL dbContext, SearchParameters searchParms, EagerLoadParameters eagerLoadParms = null)
        {
            var result = ValidSamples(dbContext, eagerLoadParms);
 
            if (searchParms!= null)
            {
                if (!String.IsNullOrWhiteSpace(searchParms.Description))
                    result = result.Where(item => item.Description.ToLower().Contains(searchParms.Description.ToLower()));
 
                if (searchParms.SomeId.HasValue)
                    result = result.Where(item => item.SomeId == searchParms.SomeId.Value);
 
                if (searchParms.IsUsed.HasValue)
                    result = result.Where(item => item.IsUsed == searchParms.IsUsed.Value);
            }
 
            return result;
        }
 
        #endregion
 
        #region Public Methods
 
        public static IEnumerable<exSample> FetchAll(ISessionHandler sessionHandler, SearchParameters searchParms, EagerLoadParameters eagerLoadParms = null)
        {
            using (var dbContext = new SignifyHRDAL(sessionHandler))
            {
                var results = FilterSamples(dbContext, searchParms, eagerLoadParms);
                                 
                return results.OrderByDescending(item => item.Id)
                        .Skip(searchParms.Skip)
                        .Take(searchParms.Take)
                        .ToList();               
            }
        }
 
        #endregion
    }
}

TO DO : Add database date fetch method

Use of sp's vs LINQ and limitations

Do not use Views and SP's directly in entity framework, use POCO classes to map objects

Entity Framework

Naming conventions

Methods for EF are written in Pascal Case

Examples of naming conventions that need to be followed for EF Methods:

Method
Fetch
TryFetch
FetchAll
FetchAllBy<Association>
TryFetchFirst
Create
Update
Delete

TODO: Examples, when is a postfix added to the above method names or not?

Which method is used for paging?

Entity Framework

Domain convention

Methods

Method Parameters Linq / Entity Operation Return Type State Dal Sync Mode
Fetch (id, optional filter params, eagerLoaded = false / eagerLoadParms = null) Single T Static Default
TryFetch (id, optional filter params, eagerLoaded = false / eagerLoadParms = null) SingleOrDefault T? Static Default
FetchAll (optional filter params) Where IEnumerable<T>, IPagedList<T> Static Default
FetchAllBy<Association> (<Association>Id, optional filter params, eagerLoaded = false / eagerLoadParms = null) Where IEnumerable<T>, IPagedList<T> Static Default
TryFetchFirst None FirstOrDefault T? Statuc Default
Create None AddObject(this) Void Non-Static Sync
Update None Attach(this) / Modified Entity State Void Non-Static Sync
Delete None Attach(this) / Deleted Entity State Void Non-Static Sync
Queryable<T>{All}{By<Association> SignifyHRDAL IQueryable IQueryable<T> Static Default
Is<Condition>, Can<Condition>, Has<Condition>, etc. None / Property not method Boolean Check Bool Non-Static Default
<Enum>Type None / Property not method <T>TryParseEnum <T> Non-Static Default
CreateEdit Call Create / Update separately from method. Is Wrapper Method Call Methods Separately Void Non-Static Sync

 

Meta Data

Meta data is used when rendering MVC Razor views and are defined in the domain if display name differs from specified name in base table, values are required and/or format of value.

Table 2 : Meta Data Properties

Property Reason Example
Required Mark value as required and performs validation when submitting from form on view. An error message can be defined to display if validation fails. [Required(ErrorMessage = "The {0} field is required")]
Display(Name=<string>) Text to display when using DisplayFor() on views. [Display(Name = "Start Date")]
DataType(<DataType>) Formats and validate input according to specified type. [DataType(DataType.DateTime)]

 

Examples

Template

Below is an example of a basic domain. This can be used as a template when creating new domains; copy and paste the code, replace all the required words (exSample, sample, smpl) according to applicable entity name.

Note: For domains with a single eager loaded objects, the following can be used:
//- Replace "EagerLoadParameters eagerLoadParms = null"
bool useEagerLoading = false
using SignifyHR.Core;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
 
namespace SignifyHR.Data.Domain
{
    public partial class exSample : IAuditable
    {
        #region Search Parameters
 
        public class SearchParameters : BaseSearchParameters
        {
            public int? SomeId { get; set; }
            public string Description { get; set; }
            public bool IsUsed { get; set; }
        }
 
        #endregion
 
        #region Eager Load Parameters
 
        public class EagerLoadParameters : BaseSearchParameters
        {
            public bool IncludeSampleDocuments { get; set; }
            public bool IncludeSampleComments { get; set; }
        }
 
        #endregion
 
        #region Protected Methods
 
        protected static IQueryable<exSample> ValidSamples(SignifyHRDAL dbContext, EagerLoadParameters eagerLoadParms = null)
        {
            var samples = dbContext.exSamples.AsQueryable();
 
            if (eagerLoadParms != null)
            {
                if (eagerLoadParms.IncludeSampleDocuments )
                    samples = samples.Include(item => item.exSampleDocuments);
 
                if (eagerLoadParms.IncludeSampleComments )
                    samples = samples.Include(item => item .exSampleComments);
            }
 
            return samples;
        }
 
        protected static IQueryable<exSample> FilterSamples(SignifyHRDAL dbContext, SearchParameters searchParms, EagerLoadParameters eagerLoadParms = null)
        {
            var result = ValidSamples(dbContext, eagerLoadParms);
 
            if (searchParms!= null)
            {
                if (!String.IsNullOrWhiteSpace(searchParms.Description))
                    result = result.Where(item => item.Description.ToLower().Contains(searchParms.Description.ToLower()));
 
                if (searchParms.SomeId.HasValue)
                    result = result.Where(item => item.SomeId == searchParms.SomeId.Value);
 
                if (searchParms.IsUsed.HasValue)
                    result = result.Where(item => item.IsUsed == searchParms.IsUsed.Value);
            }
 
            return result;
        }
 
        #endregion
 
        #region Public Methods
 
        public static exSample Fetch(ISessionHandler sessionHandler, int id, EagerLoadParameters eagerLoadParms = null)
        {
            using (var dbContext = new SignifyHRDAL(sessionHandler))
            {
                return ValidSamples(dbContext, eagerLoadParms).Single(item => item.Id == id);
            }
        }
 
        public static exSample TryFetch(ISessionHandler sessionHandler, int id, EagerLoadParameters eagerLoadParms = null)
        {
            using (var dbContext = new SignifyHRDAL(sessionHandler))
            {
                return ValidSamples(dbContext, eagerLoadParms).SingleOrDefault(item => item.Id == id);
            }
        }
 
        public static IEnumerable<exSample> FetchAll(ISessionHandler sessionHandler, SearchParameters searchParms, EagerLoadParameters eagerLoadParms = null)
        {
            using (var dbContext = new SignifyHRDAL(sessionHandler))
            {
                var results = FilterSamples(dbContext, searchParms, eagerLoadParms);
                                 
                return results.OrderByDescending(item => item.Id)
                        .Skip(searchParms.Skip)
                        .Take(searchParms.Take)
                        .ToList();               
            }
        }
 
        #endregion
 
        #region Public CRUD Actions
 
        public void Create(ISessionHandler sessionHandler)
        {
            using (var dbContext = new SignifyHRDAL(sessionHandler))
            {
                dbContext.exSamples.Add(this);
                dbContext.SaveChanges();
            }
        }
 
        public void Update(ISessionHandler sessionHandler)
        {
            using (var dbContext = new SignifyHRDAL(sessionHandler))
            {
                dbContext.exSamples.Attach(this);
                dbContext.Entry(this).State = EntityState.Modified;
                dbContext.SaveChanges();
            }
        }
                
        public void Delete(ISessionHandler sessionHandler)
        {
            using (var dbContext = new SignifyHRDAL(sessionHandler))
            {
                dbContext.exSamples.Attach(this);
                dbContext.exSamples.Remove(this);
                dbContext.SaveChanges();
            }
        }
 
        #endregion
    }
}

 

Methods

FetchAll - PagedList.IPagedList

OrderBy or OrderByDescending must be applied prior to ToPagedList.

Use PagedList.IPagedList for Bootstrap 3 Pager

using PagedList;
 
public static IPagedList<exSample> FetchAll(ISessionHandler sessionHandler, SearchParameters searchParms, EagerLoadParameters eagerLoadParms = null)
{
  using (var dbContext = new SignifyHRDAL(sessionHandler))
  {
     var results = FilterSamples(dbContext, searchParms, eagerLoadParms);
                                 
      return results.OrderByDescending(item => item.Id)
             .ToPagedList(searchParms.CurrentPage.Value, searchParms.PageSize.Value);               
  }
}

FetchAll - X.PagedList.IPagedList

OrderBy or OrderByDescending must be applied prior to ToPagedList.

Use X.PagedList.IPagedList for Bootstrap 3 Pager when returning POCO class data.

using X.PagedList;
 
 
        public static IPagedList<exSample> FetchAllPreview(ISessionHandler sessionHandler, SearchParameters searchParms, EagerLoadParameters eagerLoadParms = null)
        {
            using (var dbContext = new SignifyHRDAL(sessionHandler))
            {
                var samples = FilterSamples(dbContext, searchParms, eagerLoadParms);
 
                var results = samples.Select(item => new SamplePreview
                {
                    XSPreviewId = item.Id,
                    XSDescription = item.Description,
                    XSPath = Path.GetFileNameWithoutExtension(item.Title)
                })
 
                return results.OrderByDescending(item => item.Id)
                        .ToPagedList(searchParms.CurrentPage.Value, searchParms.PageSize.Value, results.Count());
            }
        }

Create & Update (Non-IAuditable Table)

Use the following where table definitions does not contain all columns as required by IAuditable.

public void Create(exSample sample)
{
    using (var dbContext = new SignifyHRDAL(true))
    {
        sample.SomeValue = 123;
 
        dbContext.exSample.AddObject(sample);
        dbContext.SaveChanges();
    }
}
 
public void Update(exSample sample)
{
    using (var dbContext = new SignifyHRDAL(true))
    {
        sample.SomeValue = 123;
 
        dbContext.exSample.Attach(sample);
        dbContext.ObjectStateManager.ChangeObjectState(this, EntityState.Modified);
        dbContext.SaveChanges();
    }
}

Delete Multiple

Use the following for multiple items.

public void DeleteMany(IEnumerable<exSample> sampleList)
{
    using (var dbContext = new SignifyHRDAL(true))
    {
        foreach (var sample in sampleList)
        {
            sample.Delete();
        }
    }
}

Meta Data

Remember to specify MetadataType for partial class created.

[MetadataType(typeof(exSampleMeta))]
public partial class exSample : IAuditable { ... }
 
public class exSampleMeta : DefaultColumnsMeta
{
    [Required(ErrorMessage = "The {0} field is required")]
    [Display(Name = "Sample Name")]
    public string Description { get; set; }
 
    [Required(ErrorMessage = "The {0} field is required")]
    [Display(Name = "Start Date")]
    [DataType(DataType.DateTime)]
    public DateTime StartDate { get; set; }
 
    [Required(ErrorMessage = "The {0} field is required")]
    [Display(Name = "End Date")]
    [DataType(DataType.DateTime)]
    public DateTime EndDate { get; set; }
 
    [Display(Name = "Enabled")]
    public bool Enabled { get; set; }
}

 

TODO : AddRange, UpdateRange, FetchPaged

Entity Framework

LINQ

IQueryable

This is used when you want to add and/or filter results from the database, without the query being executed already.  This is generally used with methods like ValidSamples() and FilterSamples()

The IQueryable interface inherits the IEnumerable interface so that if it represents a query, the results of that query can be enumerated.

IEnumerable

Use when you deal with in process memory object collections and loop through the collection objects.  IEnumerable uses Func objects that result in the query being executed immediately and completely, your application will see a performance boost.

Example:

using SignifyHR.Core;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
 
namespace SignifyHR.Data.Domain
{
    public partial class exSample : IAuditable
    {
        #region Search Parameters
 
        public class SearchParameters : BaseSearchParameters
        {
            public int? SomeId { get; set; }
            public string Description { get; set; }
            public bool IsUsed { get; set; }
        }
 
        #endregion
 
        #region Eager Load Parameters
 
        public class EagerLoadParameters : BaseSearchParameters
        {
            public bool IncludeSampleDocuments { get; set; }
            public bool IncludeSampleComments { get; set; }
        }
 
        #endregion
 
        #region Protected Methods
 
        protected static IEnumerable<exSample> ValidSamples(SignifyHRDAL dbContext, EagerLoadParameters eagerLoadParms = null, SearchParameters searchParms)
        {
        	//Result not executed yet
            var samples = dbContext.exSamples.AsQueryable();
 
 			//Result not executed yet
            if (eagerLoadParms != null)
            {
                if (eagerLoadParms.IncludeSampleDocuments )
                    samples = samples.Include(item => item.exSampleDocuments);
 
                if (eagerLoadParms.IncludeSampleComments )
                    samples = samples.Include(item => item .exSampleComments);
            }
            
            //Result not executed yet
            if (searchParms!= null)
            {
                if (!String.IsNullOrWhiteSpace(searchParms.Description))
                    samples = samples.Where(item => item.Description.ToLower().Contains(searchParms.Description.ToLower()));
 
                if (searchParms.SomeId.HasValue)
                    samples = samples.Where(item => item.SomeId == searchParms.SomeId.Value);
 
                if (searchParms.IsUsed.HasValue)
                    samples = samples.Where(item => item.IsUsed == searchParms.IsUsed.Value);
            }
 
 			//Result executed immediately and completely
            return samples.AsEnumerable();
        }
    }
}

 

TODO: Add example for IQueryable

As many calculations, filtering should occur in IQueryable

Naming convention for IQueryable e.g. Valid{ObjectNames}

Entity Framework

EF Core

https://docs.microsoft.com/en-us/ef/

JavaScript

JavaScript

Table of contents

JavaScript

General

Try to follow the AirBnB styling guide as far as possible. Sometimes you might have to deviate from the guide; such as when targeting Internet Explorer without a transpiler, you have to use var instead of const or let.

When using a complete toolchain with a transpiler, eslint should be configured to warn about any style violations.

 

JavaScript

Naming conventions

// bad
const OBJEcttsssss = {};
const this_is_my_object = {};
function c() {}

// good
const thisIsMyObject = {};
function thisIsMyFunction() {}
// bad
function user(options) {
  this.name = options.name;
}

const bad = new user({
  name: 'nope',
});

// good
class User {
  constructor(options) {
    this.name = options.name;
  }
}

const good = new User({
  name: 'yup',
});
// bad
import SmsContainer from './containers/SmsContainer';

// bad
const HttpRequests = [
  // ...
];

// good
import SMSContainer from './containers/SMSContainer';

// good
const HTTPRequests = [
  // ...
];

// also good
const httpRequests = [
  // ...
];

// best
import TextMessageContainer from './containers/TextMessageContainer';

// best
const requests = [
  // ...
];
JavaScript

Bundling

Always use bundling to some degree in production. Commonly used vendor scripts should always be bundles, but however be aware that bundles do not grow too large. Bundling tools such as webpack provide an efficient way to split bundles when they grow too large.

 

TODO : bundles do not grow too large - what is the guidelines here?

Example of bundling

JavaScript

Minification

Always minify in production.

 

TODO : Example and how is minification done

Use minified version when external library is used

JavaScript

jQuery

// bad
const sidebar = $('.sidebar');

// good
const $sidebar = $('.sidebar');

// good
const $sidebarBtn = $('.sidebar-btn');
// bad
function setSidebar() {
  $('.sidebar').hide();

  // ...

  $('.sidebar').css({
    'background-color': 'pink',
  });
}

// good
function setSidebar() {
  const $sidebar = $('.sidebar');
  $sidebar.hide();

  // ...

  $sidebar.css({
    'background-color': 'pink',
  });
}
// bad
$('ul', '.sidebar').hide();
$('.sidebar').find('ul').hide();
$('.sidebar').children('ul').hide();

// good
$('.sidebar ul').hide();
$('.sidebar > ul').hide();
$sidebar.find('ul').hide();
$.getJSON('http://example.com/json', (data) => {
  // Do something with the JSON data
}).fail((jqXHR, textStatus, errorThrown) => {
  // Show an error message
}).always(() => {
  // Hide a loading indicator if required
});
JavaScript

TypeScript

JavaScript

Iterators (Loops)

const numbers = [1, 2, 3, 4, 5];

// bad
let sum = 0;
for (let num of numbers) {
  sum += num;
}
sum === 15;

// good
let sum = 0;
numbers.forEach((num) => {
  sum += num;
});
sum === 15;

// best (use the functional force)
const sum = numbers.reduce((total, num) => total + num, 0);
sum === 15;

// bad
const increasedByOne = [];
for (let i = 0; i < numbers.length; i++) {
  increasedByOne.push(numbers[i] + 1);
}

// good
const increasedByOne = [];
numbers.forEach((num) => {
  increasedByOne.push(num + 1);
});

// best (keeping it functional)
const increasedByOne = numbers.map((num) => num + 1);

 

JavaScript

Properties

const luke = {
  jedi: true,
  age: 28,
};

// bad
const isJedi = luke['jedi'];

// good
const isJedi = luke.jedi;
const luke = {
  jedi: true,
  age: 28,
};

function getProp(prop) {
  return luke[prop];
}

const isJedi = getProp('jedi');
// bad
const hero = {
  firstName: 'Dana',
  lastName: 'Scully'
};

const heroes = [
  'Batman',
  'Superman'
];

// good
const hero = {
  firstName: 'Dana',
  lastName: 'Scully',
};

const heroes = [
  'Batman',
  'Superman',
];
JavaScript

Variables

Do not use const or let when targeting Internet Explorer without a transpiler.

// bad
superPower = new SuperPower();

// good
const superPower = new SuperPower();
// bad
var a = 1;
var b = 2;

// good
const a = 1;
const b = 2;
// bad
const items = getItems(),
    goSportsTeam = true,
    dragonball = 'z';

// bad
// (compare to above, and try to spot the mistake)
const items = getItems(),
    goSportsTeam = true;
    dragonball = 'z';

// good
const items = getItems();
const goSportsTeam = true;
const dragonball = 'z';
// bad
let i, len, dragonball,
    items = getItems(),
    goSportsTeam = true;

// bad
let i;
const items = getItems();
let dragonball;
const goSportsTeam = true;
let len;

// good
const goSportsTeam = true;
const items = getItems();
let dragonball;
let i;
let length;
// bad - unnecessary function call
function checkName(hasName) {
  const name = getName();

  if (hasName === 'test') {
    return false;
  }

  if (name === 'test') {
    this.setName('');
    return false;
  }

  return name;
}

// good
function checkName(hasName) {
  if (hasName === 'test') {
    return false;
  }

  const name = getName();

  if (name === 'test') {
    this.setName('');
    return false;
  }

  return name;
}
// bad
(function example() {
  // JavaScript interprets this as
  // let a = ( b = ( c = 1 ) );
  // The let keyword only applies to variable a; variables b and c become
  // global variables.
  let a = b = c = 1;
}());

console.log(a); // throws ReferenceError
console.log(b); // 1
console.log(c); // 1

// good
(function example() {
  let a = 1;
  let b = a;
  let c = a;
}());

console.log(a); // throws ReferenceError
console.log(b); // throws ReferenceError
console.log(c); // throws ReferenceError

// the same applies for `const`
// bad
const foo =
  superLongLongLongLongLongLongLongLongFunctionName();

// bad
const foo
  = 'superLongLongLongLongLongLongLongLongString';

// good
const foo = (
  superLongLongLongLongLongLongLongLongFunctionName()
);

// good
const foo = 'superLongLongLongLongLongLongLongLongString';

CSS/HTML

CSS/HTML

Table of contents

CSS/HTML

General

Try to follow the AirBnB CSS/SASS guideline as far as possible. 

Please have a look at the rest of this chapter to see more details about Naming Conventions and BEM (very important!)

CSS/HTML

Naming conventions

BEM, or “Block-Element-Modifier”, is a naming convention for classes in HTML and CSS. 

We will be using BEM for the following reasons:

Example:

HTML

<a class="btn btn--big btn--orange" href="https://css-tricks.com">
  <span class="btn__price">R199.99</span>
  <span class="btn__text">Subscribe</span>
</a>

CSS

/* Block component */
.btn {}

/* Element that depends upon the block */ 
.btn__price {}

/* Modifier that changes the style of the block */
.btn--orange {} 
.btn--big {}

 

If another developer wrote this markup, and we weren’t familiar with the CSS, we should still have a good idea of which classes are responsible for what and how they depend on one another. Developers can then build their own components and modify the existing block to their heart’s content. Without writing much CSS, developers are potentially capable of creating many different combinations of buttons simply by changing a class in the markup:

 

BEM.PNG

 

 

CSS/HTML

Can I Use

To make sure the supported browsers can render the CSS properties that you want to use, refer to the Can I Use Site.

Can I Use provides browser support tables for modern web technologies.

For example, when searching CSS Grid, you will see all the browsers (and their versions) that can support the properties:

CanIUse.PNG

TO DO : show list of browsers supported by Signify

CSS/HTML

Bundling

Always use bundling to some degree in production. Commonly used CSS files (for an Area or Module) should always be bundled.

 

TODO : Give more info and examples on CSS bundling

 

CSS/HTML

SASS

Sass is a stylesheet language that’s compiled to CSS. It allows you to use variablesnested rulesmixinsfunctions, and more, all with a fully CSS-compatible syntax. Sass helps keep large stylesheets well-organized and makes it easy to share design within and across projects.

Naming Conventions

Style rules are the foundation of Sass, just like they are for CSS. And they work the same way: you choose which elements to style with a selector, and declare properties that affect how those elements look.

Nested selectors

Do not nest selectors more than three levels deep!

.page-container {
  .content {
    .profile {
      // STOP!
    }
  }
}

When selectors become this long, you're likely writing CSS that is:

Again: never nest ID selectors!

If you must use an ID selector in the first place (and you should really try not to), they should never be nested. If you find yourself doing this, you need to revisit your markup, or figure out why such strong specificity is needed. If you are writing well formed HTML and CSS, you should never need to do this.

Please refer to SASS Style Rules for more details

Variables

Sass variables are simple: you assign a value to a name that begins with $, and then you can refer to that name instead of the value itself. But despite their simplicity, they're one of the most useful tools Sass brings to the table. Variables make it possible to reduce repetition, do complex math, configure libraries, and much more.

Prefer dash-cased variable names (e.g. $my-variable) over camelCased or snake_cased variable names. It is acceptable to prefix variable names that are intended to be used only within the same file with an underscore (e.g. $_my-variable).

Please refer to the Variables for examples and details

Mixins

Mixins allow you to define styles that can be re-used throughout your stylesheet. They make it easy to avoid using non-semantic classes like .float-left, and to distribute collections of styles in libraries.

Mixins should be used to DRY up your code, add clarity, or abstract complexity--in much the same way as well-named functions. Mixins that accept no arguments can be useful for this, but note that if you are not compressing your payload (e.g. gzip), this may contribute to unnecessary code duplication in the resulting styles.

Please refer to the @mixin and @include for examples and details

Functions

Sass provides many built-in modules which contain useful functions (and the occasional mixin). These modules can be loaded with the @use rule like any user-defined stylesheet, and their functions can be called like any other module member. All built-in module URLs begin with sass: to indicate that they're part of Sass itself.

Please refer to the Functions documentation for examples and details

Ordering of property declarations

1. Property declarations

List all standard property declarations, anything that isn't an @include or a nested selector.

.btn-green {
  background: green;
  font-weight: bold;
  // ...
}

2.  @include declarations

Grouping @includes at the end makes it easier to read the entire selector.

.btn-green {
  background: green;
  font-weight: bold;
  @include transition(background 0.5s ease);
  // ...
}

3. Nested selectors

Nested selectors, if necessary, go last, and nothing goes after them. Add whitespace between your rule declarations and nested selectors, as well as between adjacent nested selectors. Apply the same guidelines as above to your nested selectors.

.btn {
  background: green;
  font-weight: bold;
  @include transition(background 0.5s ease);

  .icon {
    margin-right: 10px;
  }
}

React

React

Design Principles and Standards

For design principles using React, use the guide below by clicking the image

image-1602134266612.png

For styling using React and JSX, use the guide below by clicking the image

image-1602133968135.png

Web API (REST)

Web API (REST)

Table of contents

1. General

2. Naming Conventions

3. Methods

4. Versioning

5. Object Usage

Web API (REST)

General

This is an extension on C# standards.

XML Documentation Commenting

Code Documenting

Ensure all publicly exposed actions, objects and properties are described using XML Documentation Comments as this is used to instruct third party users more about what an action does, what an object represents and what a property entails.

/// <summary>
/// Returns a sync structure list of all first level nodes to which user has access
/// </summary>
/// <returns></returns>
[ApiRoute("LearningPortal/AllCourses", AcceptedVersions = new[] { 1, 2, 3 })]
public IEnumerable<NodeSyncStructure> GetNodes()
{
    try
    {
        return NodeSyncStructure.FetchAll(SessionHandler, false);
    }
    catch (Exception ex)
    {
        throw LogException(HttpStatusCode.BadRequest, Request, ex);
    }
}
/// <summary>
/// Data Tranfer Object to transfer node comparison information from and to the API
/// </summary>
public class NodeSyncStructure
{
    /// <summary>
    /// The id for the node
    /// </summary>
    public int NodeId { get; set; }
    /// <summary>
    /// The latest edited date for the node - server date
    /// </summary>
    public DateTime NodeLastEditedDate { get; set; }
    /// <summary>
    /// The latest edited date for the employee node progress - server date
    /// </summary>
    public DateTime EmployeeProgressLastEditedDate { get; set; }
    /// <summary>
    /// The number of user who completed the node
    /// </summary>
    public int CompletedCount { get; set; }
}

image-1602160659345.png

Web API (REST)

Naming conventions

URI's

API URI's are based around resources. Actions are indicated with the use of HTTP Methods.

Microsoft Documentation: Organize the API around resources.

 

Code

Decorate actions and controllers according to resource path and applicable version(s).

Controllers
[ApiRoute("TrainingRequirements")]
public class TrainingRequirementsController : ApiBaseController
{
    //code logic
}
Actions
[ApiRoute("LearningPortal/Pathways/{nodeId:int}")]
public NodeStructure GetPathway(int nodeId)
{
    //code logic
}


[ApiRoute("LearningPortal/Pathways/{nodeId:int}/Favourite")]
public bool GetFavourite(int nodeId)
{
    //code logic
}

[ApiRoute("LearningPortal/Pathways/{nodeId:int}/Steps/{stepId:int}", AcceptedVersions = new[] { 1, 2 })]
public StepStructure GetPathwayStep(int nodeId, int stepId)
{
    //code logic
}

[ApiRoute("LearningPortal/Pathways/{nodeId:int}/Steps/{stepId:int}", StartingVersion = 3)]
public StepStructureV3 GetPathwayStepV3(int nodeId, int stepId)
{
    //code logic
}
Web API (REST)

Methods

Define operations in terms of HTTP methods

Conform to HTTP semantics

 

GET methods

Action: Retrieve resource(s).
HTTP Status Code(s): 200 (OK), 404 (Not Found)

 

POST methods

Action: Create resource(s) (can also be used for updates in some cases).
HTTP Status Code(s): 201 (Created), 204 (No Content), 400 (Bad Request), 200 (OK)

 

PUT methods

Action: Update resource(s).
HTTP Status Code(s): 200 (OK), 204 (No Content), 404 (Not Found), 409 (Conflict)

 

DELETE methods

Action: Delete/Remove resource(s).
HTTP Status Code(s): 204 (No Content), 404 (Not Found)

 

Web API (REST)

Versioning

URI versioning is utilised. Refer to below links for more information:

Versioning a RESTful web API

API Version Control

Service Versioning

Web API (REST)

Object Usage

Data Transfer Object - DTO

Data Transfer Object is the object returned by an API call.
A DTO is used to prevent exposing your database entity structure. This also prevent the exposing of data not being or to be used by the API client, such as linked entities. Data can also be returned as human readable if an API call is consumed by an UI client. 

/// <summary>
/// Data Tranfer Object to transfer employee node rating information from and to the API
/// </summary>
public class EmployeeNodeRating
{
    /// <summary>
    /// The id of the rating
    /// </summary>
    public int Id { get; set; }
    /// <summary>
    /// The node id for pathway rated
    /// </summary>
    public int NodeId { get; set; }
    /// <summary>
    /// The rating value specified
    /// </summary>
    public int Rating { get; set; }
    /// <summary>
    /// The comment specified for ranking
    /// </summary>
    public string Comment { get; set; }
    /// <summary>
    /// The employee name for who rated node
    /// </summary>
    public string EmployeeName { get; set; }
    /// <summary>
    /// The rating date for ranking
    /// </summary>
    public string Date { get; set; }
    /// <summary>
    /// The image url for user rating
    /// </summary>
    public string ImageUrl { get; set; }
    /// <summary>
    /// The flag to indicate if comment is own comment
    /// </summary>
    public bool IsOwnComment { get; set; }

    /// <summary>
    /// Fetch the node rating details
    /// </summary>
    /// <param name="sessionHandler"></param>
    /// <param name="nodeId"></param>
    /// <returns></returns>
    public static EmployeeNodeRating Fetch(ISessionHandler sessionHandler, int nodeId)
    {
        var eagerLoadParms = new pwEmployeeRating.EagerLoadParameters
        {
            IncludeEmployees = true,
        };

        var empRating = pwEmployeeRating.TryFetchByNodeAndEmployee(sessionHandler, nodeId, sessionHandler.EmployeeId.Value, eagerLoadParms);

        if (empRating == null)
            return null;
        else
            return new EmployeeNodeRating
            {
                Id = empRating.Id,
                NodeId = empRating.NodeId,
                EmployeeName = empRating.prsEmployee.NameSurname,
                Comment = empRating.Comment,
                Date = empRating.CreatedDate.ToShortDateString(),
                ImageUrl = String.Format("~/api/thumbnail/AspectRatioEmployee?id={0}&width=null&height=96", empRating.prsEmployee.EmployeeNumber),
                IsOwnComment = true,
                Rating = empRating.Rating
            };
    }
}

 

Value/View Object - VO

A Value/View Object is generally the object received by an API call.
This is similar to a DTO in the sense that the underlying database entity is not exposed. A VO generally only contains properties for all the applicable, editable values to be submitted by the client; validation are also performed on these values prior to submitting data to data storage / database.

/// <summary>
/// View Object to transfer employee node rating information from the API
/// </summary>
public class EmployeeNodeRating
{
    /// <summary>
    /// The id for node
    /// </summary>
    public int NodeId { get; set; }
    /// <summary>
    /// The rating value specified
    /// </summary>
    public int Rating { get; set; }
    /// <summary>
    /// The comment specified for ranking
    /// </summary>
    public string Comment { get; set; }
    /// <summary>
    /// The flag to exclude comment from updating
    /// </summary>
    public bool IgnoreCommentChange { get; set; }

    /// <summary>
    /// Log the node rating for employee
    /// </summary>
    /// <param name="sessionHandler"></param>
    /// <returns></returns>
    public void LogRating(ISessionHandler sessionHandler)
    {
        var empRating = pwEmployeeRating.TryFetchByNodeAndEmployee(sessionHandler, this.NodeId, sessionHandler.EmployeeId.Value);
        if (empRating == null)
        {
            empRating = new pwEmployeeRating
            {
                NodeId = this.NodeId,
                EmployeeId = sessionHandler.EmployeeId.Value,
                Rating = this.Rating,
                Comment = this.IgnoreCommentChange ? String.Empty : this.Comment.Trim(),
            };

            empRating.Create(sessionHandler);
        }
        else
        {
            empRating.Rating = this.Rating;
            empRating.Comment = this.IgnoreCommentChange ? empRating.Comment : this.Comment.Trim();
            empRating.Update(sessionHandler);
        }
    }
}

SQL

SQL

Table of contents

SQL

General

2018/11/30 : dbotha : 56789 : Added check for qualifications offered by my learning provider.
{Date} : {Author} : {TP#} Short concise description of change

SQL

Naming conventions

General

Note: The parent / grouping determines the module the data is stored on E.g. EmployeePDPs (an employee’s PDP’s) vs. pdpPeriod (a PDP’s periods)

Table 1 : Database Module Abbreviations

Abbreviation

Module

cc

Career Conversation

cfg

Configuration

cl

Catalogue

com

Communication (Import / Export)

cpd

Credits

dbd

Dashboards

ab

Assessment Builder

ee

Employee Evaluation

el2

E-Learning v2

el

e-Learning

els

Learning Store

em

Event Management / Training and Scheduling

fais

FAIS

ate

Ask The Expert/ Discussion Forum

icn

Icodeon

ir

Internal Relations / Disciplinary Actions

jl

Job Leveling

jp

Job Profiler

leave

Leave Management - New

lic

Licences

mc

Mentors and Coaches

ntf

Notifications

org

Organisational Structure

pdm

Performance Management

pdp

Personal Development Plan

prc

HR Processes

prs

Personnel

pw

Pathways

rb

Report Builder

rec

Recruitment

rem

Remuneration

rp

Resource Planning

rpt

Reports / Report Management

sms

SMS Notifications

sr

Salary Review

ss

Salary Scenario

sty

System Framework

sys

System Administration

tal

Talent Management - New

sc

Succession and Career Planning

tM

Talent Management - Old

txAudit

Auditing - Old

wf

Work Flows

tr_

Trigger

Tables
Views
User-Defined Functions
Stored Procedures
Temporary Tables
Common Table Expressions (CTE's)
Indexes
Constraints
SQL

Cursors

DECLARE @SchemaID INT

DECLARE curs CURSOR LOCAL FORWARD_ONLY STATIC READ_ONLY FOR
  SELECT
    SchemaID
  FROM cfgSchemaID WITH(NOLOCK)
  WHERE
    GETDATE() BETWEEN ValidFrom AND ValidTo
    AND SysID = 101

OPEN curs

FETCH NEXT FROM curs 
INTO @SchemaID

WHILE @@FETCH_STATUS = 0
BEGIN

     /*Do your commands for @SchemaID here*/ 

/*Get the next author.*/
FETCH NEXT FROM curs 
INTO @SchemaID
END

CLOSE curs
DEALLOCATE curs
SQL

Common table expressions (CTE's)

;WITH cteEmployees
     AS (SELECT
                Name
              , Surname
              , EmployeeNumber
         FROM
              prsEmployees WITH(NOLOCK))
     SELECT
            *
     FROM
          cteEmployees

The use of recursive CTEs

WITH Managers AS
(
--initialization
SELECT EmployeeID, LastName, ReportsTo 
FROM Employees
WHERE ReportsTo IS NULL
UNION ALL
--recursive execution
SELECT e.employeeID,e.LastName, e.ReportsTo
FROM Employees e INNER JOIN Managers m 
ON e.ReportsTo = m.employeeID
)
SELECT * FROM Managers 
SQL

Temporary tables

IF OBJECT_ID('tempdb..#TheTable') IS NOT NULL
BEGIN
/*Do Stuff*/
END
      CREATE TABLE #LeaveCycles(
           StartDate   DATETIME,
           EndDate     DATETIME,
           LeaveTypeId INT,
           Name        VARCHAR(200) COLLATE DATABASE_DEFAULT,
           CycleId     INT,
           CreatedDate DATETIME,
           EmployeeId  INT
                          )

      INSERT INTO #LeaveCycles(
             StartDate
           , EndDate
           , LeaveTypeId
           , Name
           , CycleId
           , CreatedDate
           , EmployeeId
                       )
      EXEC LeaveCalculateActiveCyclesByLeaveType
           @EmployeeId = @EmployeeId,
           @SchemaId = @SchemaId,
           @LeaveTypeId = @LeaveTypeId,
           @IsHistoric = @IsHistoric

 

SQL

Table variables

https://stackoverflow.com/questions/11857789/when-should-i-use-a-table-variable-vs-temporary-table-in-sql-server 

DECLARE @product_table TABLE (
    product_name VARCHAR(MAX) NOT NULL,
    brand_id INT NOT NULL,
    list_price DEC(11,2) NOT NULL
);

INSERT INTO @product_table
(
    product_name,
    brand_id,
    list_price
  )
SELECT
    product_name,
    brand_id,
    list_price
FROM
    production.products
WHERE
    category_id = 1;
SQL

Tables and indexes

SQL

User defined functions

SQL

Stored procedures

SELECT EmpNo, EmpName
FROM EmployeeRecord
WHERE Salery > 1000
EXCEPT
SELECT EmpNo, EmpName
FROM EmployeeRecord
WHERE Salery > 2000
ORDER BY EmpName;
SQL

Triggers

SQL

Existence checks

IF NOT EXISTS
              (
              SELECT TOP 1
                     1
              FROM
                   sys.all_columns c
                   JOIN sys.tables t
                      ON t.object_id = c.object_id
              WHERE t.name = 'EmployeeLeave'
                    AND c.name = 'ActionStatus')
   BEGIN
      ALTER TABLE EmployeeLeave
      ADD
                  ActionStatus INT
END
IF EXISTS
              (
              SELECT TOP 1
                     1
              FROM
                   sys.all_columns c
                   JOIN sys.tables t
                      ON t.object_id = c.object_id
              WHERE t.name = 'EmployeeLeave'
                    AND c.name = 'ActionStatus')
   BEGIN
      ALTER TABLE EmployeeLeave
      ALTER COLUMN ActionStatus NVARCHAR(2) NOT NULL
END
SQL

Views

Supported Browsers

Version 9 Supported Browsers

image-1603442840878.png