Code Modules 365


Code modules in Pims365

Code modules usually are used as RouteHandlers, Data Import/Export and Jobs

Examples bellow are created using Pims365

Minimal example of a RouteHandler

A standard RouteHandler as of 2019 uses RouteHandlerBase instead of the previous ReusableRouteHandlerBase

Like all good tutorials we start with a public RouteHandler which will return the text "Hello World!"

using System.Threading; using System.Threading.Tasks; using Appframe365.Web; using Appframe365.Web.Context; using Appframe365.Web.RouteHandlers; using Appframe365.Web.Registries.Attributes; using Appframe365.Web.JSON; namespace RouteHandlers { [RouteUrl("api/docs/helloworld")] public class HelloWorld : RouteHandlerBase { protected override async Task ProcessAsync(RequestContext pContext, CancellationToken ct) { JSON.SerializeToResponse("Hello world!", pContext); await Task.FromResult(true); } } }

Same example as above but require authentication and specific http method

Response type will be text/html.

    
    using Appframe365.Web;
    using Appframe365.Web.Context;
    using Appframe365.Web.RouteHandlers;
    using Appframe365.Web.Registries.Attributes;
    using Appframe365.Web.JSON;
    
    using System;
    using System.Web;
    using System.Net;
    using System.Threading;
    using System.Threading.Tasks;

    namespace RouteHandlers
    {
        [RouteUrl("api/docs/helloworld")]
        public class testing_class : RouteHandlerBase
        {
            protected override async Task ProcessAsync(RequestContext pContext, CancellationToken ct) {
                // Check that user is authenticated
                if(!UserContext.IsAuthenticated(pContext))
                {
                    throw new Exception("This API requires authentication");
                }
                // Check that this is a post request.
                if( pContext.Request.HttpMethod != "POST")
                    throw new HttpException((int)HttpStatusCode.MethodNotAllowed, "Method not allowed");
                    
                pContext.Response.Output.Write("Hello, I'm Appframe");
                await Task.FromResult(true);
            }
        }
    }
    

Best practice code module

    
    using Appframe365.Web.Context;
    using Appframe365.Web.Registries.Attributes;
    using Appframe365.Web.RouteHandlers;
    using Appframe365.Common.Data;
    using System;
    using System.Data;
    using System.Web;
    using System.Net;
    using System.Threading;
    using System.Threading.Tasks;

    namespace RouteHandlers
    {
        [RouteUrl("api/docs/helloworld")]
        public class PaulTest : AuthenticatedRouteHandlerBase{

            // Potentially override this property and return true in order to require Two Factor Authentication. Defaults to false
            /*protected override bool RequireTwoFactor{
                get { return true; }
            }*/
            // Potentially override this property and return true in order to require that authenticated user is developer. Defaults to false
            /*protected override bool RequireDeveloper{
                get { return true; }
            }*/

            // This method will not even get called if user is not authenticated (because it inherits from AuthenticatedRouteHandlerBase)
            protected override async Task ProcessAuthenticatedAsync(RequestContext pContext, CancellationToken ct)
            {
                // Check that this is a post request.
                if( pContext.Request.HttpMethod != "POST")
                    throw new HttpException((int)HttpStatusCode.MethodNotAllowed, "Method not allowed");
                
                // Now we can safely do the actual work.
                var rec = new afRecordSource("sviw_System_MyPerson");
                rec.SelectColumns.Add("FirstName");
                rec.SelectColumns.Add("LastName");
                rec.MaxRecords = 1;
                DataTable dt = UserContext.ForHttpContext(pContext).GetData(rec);
                string UserFirstLastName = dt.Rows[0]["FirstName"].ToString() + " " + dt.Rows[0]["LastName"].ToString();

                pContext.Response.Output.Write(String.Format("Hello, I'm Appframe, you are \"{0}\" (authenticated) and this is a post request.", UserFirstLastName));

                await Task.FromResult(true);
            }
        }
    }
    

Passing data to a RouteHandler

There are many ways of passing data to a RouteHandler

By far the most common are RouteHandler Parameters and JSON

RouteHandler Parameters
using System.Threading; using System.Threading.Tasks; using Appframe365.Web; using Appframe365.Web.Context; using Appframe365.Web.RouteHandlers; using Appframe365.Web.Registries.Attributes; using Appframe365.Web.JSON; namespace RouteHandlers { [RouteUrl("api/docs/hello/{Target}/{Parameter}")] //Router with two parameters named Target and Parameter public class HelloWorld : RouteHandlerBase { private class RouteParameters : RouteParametersBase { public string Target { get; set; } //Parameter name needs to match a name in the RouteURL public string Parameter { get; set; } //Parameter name needs to match a name in the RouteURL public RouteParameters(RequestContext pContext) : base(pContext) { } } protected override async Task ProcessAsync(RequestContext pContext, CancellationToken ct) { //Create an instance of RouteParameters RouteParameters vParameters = new RouteParameters(pContext); //Serialize to Response JSON.SerializeToResponse("Hello " + vParameters.Target + ". Parameter: " + vParameters.Parameter + "!", pContext); await Task.FromResult(true); } } }
Passing JSON Data
using System.Collections; using System.Collections.Generic; //Needed for Dictionary using System.Threading; using System.Threading.Tasks; using Appframe365.Web; using Appframe365.Web.Context; using Appframe365.Web.RouteHandlers; using Appframe365.Web.Registries.Attributes; using Appframe365.Web.JSON; namespace RouteHandlers { [RouteUrl("api/docs/hellojson")] public class HelloWorld : RouteHandlerBase { protected override async Task ProcessAsync(RequestContext pContext, CancellationToken ct) { //Create a reference to pContext.JsonParameters to shorten writing :P var vJsonParams = pContext.JsonParameters; //pContext.JsonParameters is a Dictionary of string, object, lookup Dictionary on MSDN for more info. string vJsonTarget; if(vJsonParams.ContainsKey("Target")) { vJsonTarget = vJsonParams["Target"].ToString(); } else { vJsonTarget = "Default Value if Target isn't provided"; } //This can be shortened down like this: vJsonTarget = vJsonParams.ContainsKey("Target") ? vJsonParams["Number"].ToString() : "Default Value if Target isn't provided";; //Serialize to Response JSON.SerializeToResponse("Hello " + vJsonTarget + "!", pContext); await Task.FromResult(true); } } }

Communicating with the Database

There are many ways of communicating with the database but here are the most common

Data management in Code Modules
    
    using System; //Needed for GUID;
    using System.Data; //Needed for DataTable
    using System.Collections.Generic; //Needed for Dictionary
    using System.Threading;
    using System.Threading.Tasks;
    
    using Appframe365.Web;
    using Appframe365.Web.Context;
    using Appframe365.Web.RouteHandlers;
    using Appframe365.Web.Registries.Attributes;
    using Appframe365.Web.JSON;
    using Appframe365.Common.Data;
    
    namespace RouteHandlers {
    	[RouteUrl("api/docs/hello/{Operation}")]
        [RouteConstraint("Operation", "(create|retrieve|update|destroy)")] //Constrain the Operation to the following options
    	//Why create, retrieve, update and destroy, google CRUD if you're insterrested, TL;DR is that we use it internally in Appframe :P
        public class HelloWorld : RouteHandlerBase {
            private class RouteParameters : RouteParametersBase {
                public string Operation { get; set; }
                public RouteParameters(RequestContext pContext) : base(pContext) { }
            }
    
            protected override async Task ProcessAsync(RequestContext pContext, CancellationToken ct) {        
                //Create an instance of RouteParameters
                RouteParameters vParameters = new RouteParameters(pContext);
    
                //Retrieve current UserContext
                var vUserContext = UserContext.ForHttpContext(pContext);
    
                switch(vParameters.Operation) {
                    case "create": //Same as INSERT in SQL
                        Dictionary vNewRecord = new Dictionary();
                        vNewRecord.Add("Navn", "TestLokasjon");
                        vNewRecord.Add("Hmsregnr", 123456);
                        vNewRecord.Add("GeoJson", "{\"type\":\"Point\",\"coordinates\":[10.799933,59.898145]}");
    
                        vUserContext.AddData("atbv_Hmsreg_Lokasjoner", vNewRecord, null, "atbl_Hmsreg_Lokasjoner");
                        break;
                    case "retrieve": //Same as SELECT in SQL
                        afRecordSource vSource = new afRecordSource("aviw_Hmsreg_Lokasjoner");
                        vSource.MaxRecords = 1;
                        vSource.SelectOnly("Navn", "Hmsregnr", "GeoJson");
                        DataTable vDataTable = vUserContext.GetData(vSource);
                        
                        JSON.SerializeToResponse(vDataTable, pContext);
                        break;
                    case "update": //SAME as UPDATE in SQL
                        Dictionary vNewValues = new Dictionary();
                        vNewValues.Add("Navn", "TestLokasjon");
                        vNewValues.Add("Hmsregnr", 123456);
                        vNewValues.Add("GeoJson", "{\"type\":\"Point\",\"coordinates\":[10.799933,59.898145]}");
                        
                        vUserContext.PutData("atbv_Hmsreg_Lokasjoner", vNewValues, null, "atbl_Hmsreg_Lokasjoner", new Guid(), null);
                        break;
                    case "destroy": //SAME as DELETE in SQL
                        vUserContext.DelData("atbv_Hmsreg_Lokasjoner", new Guid());
                        break;
                }
    
                await Task.FromResult(true);
            }
    	}
    }
    
    //Try to ignore these tags... formatting errors
    
    
    

Async Exception Handling

Anyone who have tried writing new Code Modules will know that using the old ErrorHandler methods doesn't work very well...
So here's one way to work around it...

        
            using System; //Needed for Exception;
            using System.Threading;
            using System.Threading.Tasks;
            
            using Appframe365.Web;
            using Appframe365.Web.Context;
            using Appframe365.Web.RouteHandlers;
            using Appframe365.Web.Registries.Attributes;
            using Appframe365.Web.JSON;
            using Appframe365.Common.Data;
            
            namespace RouteHandlers {
            	[RouteUrl("api/docs/hello/exceptions")]
                public class HelloWorld : RouteHandlerBase {
                    protected override async Task ProcessAsync(RequestContext pContext, CancellationToken ct) {        
                        
                        //This seems very messy but its the only way I have found to deal with async and try catch...
                        Exception exampleException = null;
                        try {
                            /* Some code which can fail goes here */
                        } catch (Exception ex) {
                            exampleException = ex;
                            //Can't use await inside a catch statement...
                            //So we move the exception out and handle it after...
                        }
            
                        if(exampleException != null) {
                            await ErrorHandler.HandleExceptionAsync(exampleException, pContext);
                            await ErrorHandler.WriteJSONErrorToResponseAsync(exampleException, pContext);
                        }
            
                        await Task.FromResult(true);
                    }
            	}
            }
        
    

Migration to async methods cheat sheet

    
    var vSiteAliasContext = pContext.GlobalContext.GetSiteAlias("realSiteAlias"); 
    /* to */
    var vSiteAliasContext = await pContext.GlobalContext.GetSiteAliasContextAsync("realSiteAlias"); 

    var vSiteAliasContext = pContext.GlobalContext.DefaultSiteAlias; 
    /* to */
    var vSiteAliasContext = await pContext.GlobalContext.GetDefaultSiteAliasContextAsync(); 

    var vRaised = ErrorHandler.RaiseErrorEvent(pException, pContext); 
    /* to */
    var vRaised = await ErrorHandler.RaiseErrorEventAsync(pException, pContext); 

    ErrorHandler.WriteErrorToResponse(pException, pContext, vLogged); 
    /* to */
    await ErrorHandler.WriteErrorToResponseAsync(pException, pContext); 

    ErrorHandler.WriteTextErrorToResponse(pException, pContext, vLogged); 
    /* to */
    await ErrorHandler.WriteTextErrorToResponseAsync(pException, pContext); 

    ErrorHandler.WriteJSONErrorToResponse(pException, pContext, vLogged); 
    /* to */
    await ErrorHandler.WriteJSONErrorToResponseAsync(pException, pContext); 

    var vHandled = ErrorHandler.HandleException(pException, pContext); 
    /* to */
    var vHandled = await ErrorHandler.HandleExceptionAsync(pException, pContext); 

    var vRequestContext = RequestContext.Current; 
    /* or */
    var vRequestContext = RequestContext.ForHttpContext(pContext); 
    /* to */
    var vRequestContext = await RequestContext.ForHttpContextAsync(pContext); 

    /* to get HttpContextBase from HttpContext*/
    var vContext = new HttpContextWrapper(pContext)
    
Right now appframe razor builder does not supports async methods in razor code, so dont use async methods in razor, instead use old methods.

Related articles

Placeholder "LocalizeWeb2016" failed