ASP.NET web framework in pure VB.NET code optimized for creation of Business Applications
Created as simplified and lightweight alternative to other ASP.NET frameworks like ASP.NET MVC
- simple and straightforward in development and maintenance
- MVC-like - code, data, templates are split
- code consists of: controllers, models, framework core and optional 3rd party libs
- uses ParsePage template engine
- data stored by default in SQL Server database using db.net
- RESTful with some practical enhancements
- integrated auth - simple flat access levels auth
- UI based on Bootstrap 4 with minimal custom CSS - it's easy to customzie or apply your own theme
- use of well-known 3rd party libraries: jQuery, jQuery Form, jGrowl, markdown libs, etc...
http://demo.engineeredit.com/ - this is how it looks in action right after installation before customizations
- put contents of
/www
into your webserver's public html folder - edit
/www/web.config
for database connection_string - create database from
/www/App_Data/sql/fwdatabase.sql
- open site in your browser and login with credentials as defined in database.sql (remember to change password)
- in Visual Studio do
File -> Open -> Web Site...
(Shift+Alt+O) and open/www
folder - open
Tools -> NuGet Package Manager -> Package Manager Console
and run commandUpdate-Package -reinstall
- press Ctrl+F5 to run (or F5 if you really need debugger)
- review debug log in
/www/App_Data/logs/main.log
- edit or create new controllers and models in
/www/App_Code/controllers
and/www/App_Code/models
- modify templates in
/www/App_Data/template
/App_Code - all the VB.NET code is here
/controllers - your controllers
/fw - framework core libs
/models - your models
/App_Data - non-public directory
/sql - initial database.sql script and update sql scripts
/template - all the html templates
/logs/main.log - application log (ensure to enable write rights to /logs dir for IIS)
/assets - your web frontend assets
/css
/fonts
/img
/js
/bin - additional .net dlls will be here if you install something from Nuget
/upload - upload dir for public files
/error.html - default error.html
/robots.txt - default robots.txt (empty)
/favicon.ico - change to your favicon!
/web.config - settings for db connection, mail, logging and for IIS/.NET stuff too
Controllers automatically directly mapped to URLs, so developer doesn't need to write routing rules:
GET /Controller
- list viewIndexAction()
GET /Controller/ID
- one record viewShowAction()
GET /Controller/ID/new
- one record new formShowFormAction()
GET /Controller/ID/edit
- one record edit formShowFormAction()
GET /Controller/ID/delete
- one record delete confirmation formShowDeleteAction()
POST /Controller
- insert new recordSaveAction()
PUT /Controller
- update multiple recordsSaveMultiAction()
POST/PUT /Controller/ID
- update recordSaveAction()
POST/DELETE /Controller/ID
- delete record ($_POST should be empty)DeleteAction()
GET/POST /Controller/(Something)[/ID]
- call for arbitrary action from the controllerSomethingAction()
For example GET /Products
will call ProductsController.IndexAction()
And this will cause rendering templates from /www/App_Data/templates/products/index
highlighted as bold is where you could place your code.
FW.run()
FwHooks.initRequest()
- place code here which need to be run on request start
fw.dispatch()
- performs REST urls matching and calls controller/action, if no controller found callsHomeController.NotFoundAction()
, if no requested action found in controller - calls controller action defined in contoller'sroute_default_action
(either "index" or "show")fw._auth()
- check if user can access requested controller/action, also performs basic CSRF validationfw.call_controller()
SomeController.init()
- place code here which need to be run every time request comes to this controllerSomeController.SomeAction()
- your code for particular actionSomeModel.someMethod()
- controllers may call model's methods, place most of your business logic in models
fw.Finalize()
-
GET /Admin/Users
FwHooks.initRequest()
AdminUsers.init()
AdminUsers.IndexAction()
- then ParsePage parses templates from
/template/admin/users/index/
-
GET /Admin/Users/123/edit
FwHooks.initRequest()
AdminUsers.init()
AdminUsers.ShowFormAction(123)
Users.one(123)
- then ParsePage parses templates from
/template/admin/users/showform/
-
POST /Admin/Users/123
FwHooks.initRequest()
AdminUsers.init()
AdminUsers.SaveAction(123)
Users.update(123)
fw.redirect("/Admin/Users/123/edit")
//redirect back to edit screen after db updated
Frequently asked details about flow for the IndexAction()
(in controllers inherited from FwAdminController
and FwDynamicController
):
initFilter()
- initializesMe.list_filter
from query string filter params&f[xxx]=...
, note, filters remembered in sessionsetListSorting()
- initializesMe.list_orderby
based onlist_filter("sortby")
andlist_filter("sortdir")
, also usesMe.list_sortdef
andMe.list_sortmap
which can be set in controller'sinit()
or inconfig.json
setListSearch()
- initializesMe.list_where
based onlist_filter("s")
andMe.search_fields
setListSearchStatus()
- add toMe.list_where
filtering bystatus
field if such field defined in the controller's modelgetListRows()
- query database and save rows toMe.list_rows
(only current page based onMe.list_filter("pagenum")
andMe.list_filter("pagesize")
). Also setsMe.list_count
to total rows matched by filters andMe.list_pager
for pagination if there are more than one page. UsesMe.list_view
,Me.list_where
,Me.list_orderby
You could either override these particular methods or whole IndexAction()
in your specific controller.
The following controller fields used above can be defined in controller's init()
or in config.json
:
Me.list_sortdef
- default list sorting in format: "sort_name[ asc|desc]"Me.list_sortmap
- mapping for sort names (fromlist_filter["sortby"]
) to actual db fields, Hashtablesort_name => db_field_name
Me.search_fields
- search fields, space-separatedMe.list_view
- table/view to use ingetListRows()
, if empty model'stable_name
used
Application configuration available via fw.config([SettingName])
.
Most of the global settings defined in web.config
<appSettings>
section. But there are several caclulated settings:
SettingName | Description | Example |
---|---|---|
hostname | set from server variable HTTP_HOST | osalabs.com |
ROOT_DOMAIN | protocol+hostname | https://osalabs.com |
ROOT_URL | part of the url if Application installed under sub-url | /suburl if App installed under osalabs.com/suburl |
site_root | physical application path to the root of public directory | C:\inetpub\somesite\www |
template | physical path to the root of templates directory | C:\inetpub\somesite\www\App_Data\template |
log | physical path to application log file | C:\inetpub\somesite\www\App_Data\logs\main.log |
tmp | physical path to the system tmp directory | C:\Windows\Temp |
In FwDynamicController
controller behaviour defined by /template/CONTROLLER/config.json
. Sample file can be fount at /template/admin/demosdynamic/config.json
This config file allows to define/override several properties of the FwController
(for example: as model
, save_fields
, search_fields
, list_view
,...) as well as define configuration of Show (show_fields
) and ShowForm (showform_fields
) screens. Note is_dynamic_show
and is_dynamic_showform
should be set to true accordingly.
There are samples for the one show_fields
or showform_fields
element:
//minimal setup to display the field value
{
"type": "plaintext",
"field": "iname",
"label": "Title"
},
Renders:
<div class="form-row">
<label class="col-form-label">Title</label>
<div class="col">
<p class="form-control-plaintext">FIELD_VALUE</p>
</div>
</div>
//more complex - displays dropdown with values from lookup model
{
"type": "select",
"field": "demo_dicts_id",
"label": "DemoDicts",
"lookup_model": "DemoDicts",
"is_option0": true,
"class_contents": "col-md-3",
"class_control": "on-refresh"
},
Renders:
<div class="form-row">
<label class="col-form-label">DemoDicts</label>
<div class="col-md-3">
<select id="demo_dicts_id" name="item[demo_dicts_id]" class="form-control on-refresh">
<option value="0">- select -</option>
... select options from lookup here...
</select>
</div>
</div>
Field name | Description | Example |
---|---|---|
type | required, Element type, see values in table below | select - renders as <select> html |
field | Field name from database.table or arbitrary name for non-db block | demo_dicts_id - in case of select id value won't be displayed, but used to select active list element |
label | Label text | Demo Dictionary |
lookup_model | Model name where to read lookup values | DemoDicts |
is_option0 | only for "select" type, if true - includes <option value="0">option0_title</option> |
false(default),true |
is_option_empty | only for "select" type, if true - includes <option value="">option0_title</option> |
false(default),true |
option0_title | only for "select" type for is_option0 or is_option_empty option title | "- select -"(default) |
required | make field required (both client and server-side validation), for showform_fields only |
false(default),true |
maxlength | set input's maxlength attribute, for showform_fields only |
10 |
max | set input type="number" max attribute, for showform_fields only |
999 |
min | set input type="number" min attribute, for showform_fields only |
0 |
step | set input type="number" step attribute, for showform_fields only |
0.1 |
placeholder | set input's maxlength attribute, for showform_fields only |
"Enter value here" |
autocomplete_url | type="autocomplete". Input will get data from autocomplete_url?q=%QUERY where %QUERY will be replaced with input value, for showform_fields only |
/Admin/SomeLookup/(Autocompete) |
is_inline | type radio or yesno . If true - place all options in one line, for showform_fields only |
true(default),false |
rows | set textarea rows attribute, for showform_fields only |
5 |
class | Class(es) added to the wrapping div.form-row |
mb-2 - add bottom margin under the control block |
attrs | Arbitrary html attributes for the wrapping div.form-row |
data-something="123" |
class_label | Class(es) added to the label.col-form-label |
col-md-3(default) - set label width |
class_contents | Class(es) added to the div that wraps input control |
col(default) - set control width |
class_control | Class(es) added to the input control to change appearance/behaviour | "on-refresh" - forms refreshes(re-submits) when input changed |
attrs_control | Arbitrary html attributes for the input control | data-something="123" |
help_text | Help text displayed as muted text under control block | "Minimum 8 letters and digits required" |
admin_url | For type="plaintext_link", controller url, final URL will be: "<~admin_url>/<~lookup_id>" | /Admin/SomeController |
lookup_id | to use with admin_url, if link to specific ID required | 123 |
att_category | For type="att_edit", att category new upload will be related to | "general"(default) |
validate | Simple validation codes: exists, isemail, isphone, isdate, isfloat | "exists isemail" - input value validated if such value already exists, validate if value is an email |
Type | Description |
---|---|
available for both show_fields and showform_fields | |
plaintext | Plain text |
plaintext_link | Plain text with a link to "admin_url" |
markdown | Markdown text (server-side rendered) |
noescape | Value without htmlescape |
float | Value formatted with 2 decimal digits |
checkbox | Read-only checkbox (checked if value equal to true value) |
date | Date in default format - M/d/yyyy |
date_long | Date in logn forma - M/d/yyyy hh:mm:ss |
multi | Multi-selection list with checkboxes (read-only) |
att | Block for displaying one attachment/file |
att_links | Block for displaying multiple attachments/files |
added | Added on date/user block |
updated | Updated on date/user block |
available only showform_fields | |
group_id | ID with Submit/Cancel buttons block |
group_id_addnew | ID with Submit/Submit and Add New/Cancel buttons block |
select | select with options html block |
input | input type="text" html block |
textarea | textaread html block |
input type="email" html block | |
number | input type="number" html block |
autocomplete | input type="text" with autocomplete using "autocomplete_url" |
multicb | Multi-selection list with checkboxes |
radio | radio options block |
yesno | radio options block with Yes(1)/No(2) only |
cb | single checkbox block |
date_popup | date selection input with popup calendar block |
att_edit | Block for selection/upload one attachment/file |
att_links_edit | Block for selection/upload multiple attachments/files |
Main and recommended approach - use fw.logger()
function, which is available in controllers and models (so no prefix required).
Examples: logger("some string to log", var_to_dump)
, logger(LogLevel.WARN, "warning message")
All logged messages and var content (complex objects will be dumped wit structure when possible) written on debug console as well as to log file (default /App_Data/logs/main.log
)
You could configure log level in web.config
- search "log_level" in appSettings
Another debug function that might be helpful is fw.rw()
- but it output it's parameter directly into response output (i.e. you will see output right in the browser)
- naming conventions:
- table name:
user_lists
(lowercase, underscore delimiters is optional) - model name:
UserLists
(UpperCamelCase) - controller name:
UserListsController
orAdminUserListsController
(UpperCamelCase with "Controller" suffix) - template path:
/template/userlists
- table name:
- keep all paths without trailing slash, use beginning slash where necessary
- db updates:
- first, make changes in
/App_Data/sql/database.sql
- this file is used to create db from scratch - then create a file
/App_Data/sql/updateYYYY-MM-DD.sql
with all the CREATE, ALTER, UPDATE... - this will allow to apply just this update to existing database instances
- first, make changes in
- use
fw.route_redirect()
if you got request to one Controller.Action, but need to continue processing in another Controller.Action- for example, if for a logged user you need to show detailed data and always skip list view - in the
IndexAction()
just usefw.routeRedirect("ShowForm")
- for example, if for a logged user you need to show detailed data and always skip list view - in the
- uploads
- save all public-readable uploads under
/www/upload
(default, see "UPLOAD_DIR" inweb.config
) - for non-public uploads use
/www/App_Data/upload
- or
S3
model and upload to the cloud
- save all public-readable uploads under
- put all validation code into controller's
Validate()
. See usage example inAdminDemosController
- use
logger()
and review/App_Data/logs/main.log
if you stuck- make sure you have "log_level" set to "DEBUG" in
web.config
- make sure you have "log_level" set to "DEBUG" in