Skip to content

Latest commit

 

History

History
360 lines (215 loc) · 18 KB

README.md

File metadata and controls

360 lines (215 loc) · 18 KB

AspNetMvcWebpack-Template

Base template for single stack web applications with a c# asp.net/mvc and mysql backend, and typescript/sass/razor frontend. Using Webpack and Gaspar build tools.

Structure

This template follows the MVC pattern. At the backend there are C# Models and Controllers (and other support files you need). At the front end there are Razor Views each supported by a Typescript and SASS file. There are also global Typescript and SASS files accessible to the whole application.

The end result is designed to be as compact and efficient as possible on the front end, minimising the number of HTTP calls, and ensuring the files downloaded are as minimal as possible.

This is the contents of the Template, with config and minor support files removed.

  • /Template is the main project folder.

    • /Template/Controllers contains the MVC controller classes.

    • /Template/Models contains the MVC model classes.

    • /Template/Scripts contains global site-wide typescript.

      • /Template/Scripts/BRLibraries contains some basic javascript libraries.

      • /Template/Scripts/Models can be used for .ts models

        • /Template/Scripts/Models/~csharpe.ts is the Gaspar export of c# models for use in typescript.
      • /Template/Scripts/Site.ts is the main entry point for the application, this will be complied into Javascript along with any files it references other unreferenced ts files will not be included). This will be loaded as a separate javascript file and (hopefully) cached by the browser for the whole site.

    • /Template/Styles/Site.scss contains global site-wide scss. This will be loaded as a separate css file and (hopefully) cached by the browser for the whole site.

    • /Templates/Views contains the MVC view files

      • /Template/Views/Home/ contains the files for the HomeController. For every page should have a .cshtml file (as usual) plus a.tsand.scss file. The three files will be reassembled into the final html delivered to the browser.

        You can reference any other typescript in these .ts files, this will be packaged onto that page, unless it is already provided via Site.ts.

      • /Template/Views/Shared/_Layout.cshtml is the standard template for other razor files. In here is all the code that combines js and css into the final file

    • Template/wwwroot contains images and other static assets. You can reference images from sass files relative to the .scss file; the reference will be fixed by webpack.

  • /Template.Library is for shared code shared between projects (for when you add a second project). For now it contains the Data Access Layer models for mysql.

Rename the template

If you create a repository directly from this template in GitHub, or check in a copy of this to GitHub, everything will be automatically renamed for you thanks to the GitHub workflow. You need to wait a couple of minutes for the action to run.

If you need to do it manually:

  • Use Find and Replace to search within the files for 'Template' and replace with your project name; without spaces

  • Rename the following file and folders, replacing 'Template' with your project name, as above:

    • /Template.sln

    • /Template/Template.csproj

    • /Template.Library/Template.Library.csproj

    • /Template/

    • /Template.Library/

Running the Application Locally

Once you have created a new repository from this template you should run the template to make sure everything is working as you expect.

Using Visual Studio Code

The best way to run the application is using Visual Studio Code.

To run, open the template root in a new Code window; then in the terminal type:

npm start

This will:

  • Install required nuget and npm packages (on the first run or if they go missing) npm install is triggered by the dotnet build

  • Clean the build output, deleting files generated during a build.

  • Build the .Net (BE) and Webpack (FE) parts of the application.

  • Trigger dotnet watch and webpack watch to monitor for changes and rebuild on the fly (this step triggers the build step above).

  • Launch the application in your default browser (triggered by dotnet watch)

Using Visual Studio

You can also run the application using Visual Studio, and this is often easier when debugging the c# backend code.

To run in Visual Studio, open the Template.sln file and run the project as you would normally. The build process with trigger a Webpack build and ensure all the frontend files are ready.

Using the Command Line

You can run the following commands from the root of the template:

  • npm start To clean, build and watch the project* with dotnet and webpack.

  • npx webpack To build webpack resources (js/css) for the default project.

  • npx webpack --env project=[project_name] To build webpack resources (js/css) for the named project (e.g. npx webpack --env project=Template).

  • dotnet [ build | clean | run | watch | publish ] --project=[project_name] To run dotnet commands as usual, project must be supplied from the root. dotnet build also builds webpack resources. dotnet clean cleans webpack resources. dotnet publish builds webpack resources for production.

*if you have multiple projects in your solution (e.g. a website and admin app), you will be asked which one should start (or you can choose all). If you would like to always start the same project (and skip being asked), add the following to ./.template-scripts/config.json

"defaultProject": "PROJECT_NAME"

Writing your code

Typescript / Javascript

Global Scripts

Global scripts all start from Template class in the /Scripts/Site.ts folder (renamed to you solution name). This file will be complied by webpack and loaded (and cached) separately by the browser. Additional and supporting classes can all be added to the /Scripts folder (although will work from anywhere), these files will be complied into the final javascript file if they are referenced from Site.ts.

The loaded and initialised Template class can be accessed from anywhere as window.site, for example, see how the navigation link calls the global function on click:

<a href="/" onclick="site.navClicked(event)">Home</a>

This calls the navClicked function in Site.ts. event is optional, but included to demonstrate what is possible.

Local Page Scripts

Each page can have it's own local typescript file, as shown in the structure above. It should be included alongside it's .cshtml file and have the same filename, with a .ts extension. This file will be complied into javascript and injected into the loaded page.

The file should include a starting class named after the controller and view. See the sample file at /Views/Home/Index.ts:

import { Template } from '../../Scripts/Site'

export class HomeIndex {
    constructor(private site: Template, private data: any | null) {
    }
}

The controller (view folder) is called Home and the action (or page/cshtml) is called Index, so the class is called HomeIndex

If the file exists, this class will be loaded and initialised on page load. It's constructor is passed two arguments:

  • site is a reference to the initialised global site class (same as window.site)

  • data is the json data this page is loaded with (see below); you should update any | null to the correct type once you know it.

Your local typescript file can reference any other script files in the solution. If the referenced script has already been used by the global script, it will simply use that pre-loaded version; however if it is only referenced in the page it will be injected onto the page with the rest of the javascript (thanks to WebPack magic!).

If you are only referencing external files on local pages, but want to code in the global script file, import the file in Site.ts as import './filename';

The loaded and initialised page class can be accessed from anywhere as window.page, for example, see how the demo button calls the local function on click:

<input type="button" value="Demo Button" onclick="page.buttonClick(event)" />

This calls the buttonClick function in HomeIndex.ts. event is optional, but included to demonstrate what is possible.

window.page is defined in Site.ts so page functions can be accessed from global scripts. As the type will change it is defined as any and functions should always be tested for before called - see the windowResize code

Passing data to the page on load

You can collect data at anytime after the page has loaded using API's and the Gaspar service classes (see the APIDemoController and the call to it's post method in Site.ts); although often its nice to have some data preloaded from the server on page load.

There are two options for passing data to the page; the first is following the standard MVC model; returning View(model) from the action and receiving it in the cshtml file using the @model declaration at the top of the page (see Views in ASP.NET Core MVC)

However if you want the data available for typescript to manipulate or act on; you can pass it to the data argument of the page constructor (see above). To do this, add your model/data to a jsData property in ViewData as follows:

public IActionResult Index()
{
    ViewData["jsData"] = new MyModel();
    return View();
}

Simply doing this, will populate the data property; for the above. You should change the constructor data type as follows:

constructor(private site: Template, private data: MyModel) {

In C#, MyModel should have the [ExportFor(Gaspar.TypeScript)] declaration to make MyModel available within Typescript.

Node Modules

Node modules can be added and referenced in your typescript files as you would expect; they follow the above rules and will only be packed if referenced.

*Note; while there are a large number of node packages in the template, they are all only used for build processes and not included in the final app. This template exports significantly less code than other webpack implementations; especially around the css.

Source Maps

Source Maps are generated for all complied typescript files during development (not in a published app).

The local page javascript that is injected into the page is slightly more complicated than usual due to where it appears in the source html. It is injected in _Layout.cshtml with the line @Html.Raw(embededJs)

If you edit the _Layout.cshtml file, adding lines above @Html.Raw(embededJs) (pushing it down the page); you will need to update the embededJsMapOffset offset value in the ./template-scripts/config.json file.

Window Resizes

Some designs require element sizing based on the window size; and resizing if the window size changes. In Site.ts you will see a windowResize() function, this will be called on load and whenever the window is resized; put your resizing logic in that function. It will check for and call (if found) windowResize() on the page; implement this if you have page specific elements that need to be sized.

SCSS / CSS

CSS should be added as SCSS into one of these locations:

  • /Styles/Site.scss to be available for the whole application. The compiled css version of this file will be loaded (and cached) separately by the browser.

  • /Views/{section}/{page}.scss to be available only to a given page in the application (see the example in /Views/Home/Index.scss). The complied css version of this file will be injected into the loaded page

If you would like to separate some of the global styles into separate files, reference each additional file at the top Site.scss with:

@use './MoreStyles.scss';

The contents of the local page scss file will all be prefixed with the page name on build to ensure css in here doesn't affect global content; this works using the class declaration on the content section in Layout.cshtml:

<div id="content" class="@(controller)@(view)">        
    @RenderBody()
</div>

So for the default home page (/Views/Home/Index.cshtml), the class will be HomeIndex and all the css generated from /Views/Home/Index.scss will be prefixed with .HomeIndex.

If you would like to adjust the styling of the content div from within the local page scss file, you can do so as follows:

&#content {
    padding-top: 120px;
    //...
}

Images

Images should be added to the wwwroot folder (or subfolders) in their web-ready form. The template doesn't currently use any webpack image tools (although may do in the future).

To reference the images from scss files use the relative local path from the scss file; this will help Visual Studio Code autocomplete paths as you type. The paths will be corrected on build.

To reference the images from cshtml, or other locations, paths should start with / to reference the wwwroot folder.

Adding MySql

The template is ready to connect to MySql, simply:

  • Add a new file in the solution root called secrets.json and add your connection string into that file:

    {
      "ConnectionStrings": {
        "MySql": "server=1.1.1.1;userid=XX;pwd=XX;port=3306;database=DB"
      }
    }

    Note; the secrets file lives in the root of your solution (alongside the .sln file) to make it available to all projects in the solution, as they are likely to share the same resources (e.g. database). When a project is published, using the provided publish scripts; it will be copied to the project root and will work from there (see the code in Program.cs).

    secrets.json is included in the .gitignore file, so will not be checked in, and can safely be used for other passwords and access tokens.

  • Add the database context to your controllers; in the existing HomeController.cs file, you will find the code commented out ready to use. Uncomment lines 14, 19 and 24.

  • Finally add your tables as classes with the library DAL folder (Table.cs is an example that will need to be deleted) and add your tables to ~MySqlContext.cs I need to find a way to automate this...

Publishing the Application

When publishing (via dotnet publish), webpack will be run in production mode which will minify the javascript and css, and remove map files and debugger statements.

There are GitHub publish scripts in the ./.github/-workflows folder (currently only for Azure) note the - before workflows to prevent this running until you are ready. This may be your preferred way to publish, but if you prefer manual publishing, or for occasional use; read on:

A publish script is provided to package the published applications and upload the files to your server. The script currently only supports publish to Azure Web App; other services can be added (see ./.template-scripts/publish/).

To publish the application, run:

npm run publish

If you have multiple projects in your solution (e.g. a website and admin app), you will be asked which one to publish (or you can choose all). If you would like to always publish the same project (and skip being asked), add the following to ./.template-scripts/config.json.

"defaultProject": "PROJECT_NAME"

For the publish to run, you will need to provide the publish details in the config file at ./template-scripts/config.json. If you would like to prevent the details from being committed to source control, you can add them to ./template-scripts/secrets.json instead.

To publish to Azure Web App

Azure Web App publish requires the Azure CLI. You need to add the following configuration:

{
  "Publish": {
    "Service": "AzureWebApp",
    "ResourceGroup": "resource_group_name",
    "AppName": "app_name"
  }
}

No credentials are required; you will login via a browser.

Replacements during publish

If you want to replace some text in your published files; for example to insert text that cannot be committed to source control, you can use the Replacements feature of the publish script. To do this, within the Publish config object you can add, for example:

"Replacements": [{
    "File": //file path and name, relative to the publish folder
    "Replace": //text or regex to find in the file
    "With": //text to insert
}, {...}]

Updating from the Template

If this template is updated, you can copy the changes into your application by running:

npm run update-template

This process is still quite manual, but the script hopes to make it as easy as possible. You should only run it in a source controlled environment, as the changes will be made directly to your project, and will need careful manual review.

The first time you run the script you'll be asked when to update from; which will default to your first commit. The date last updated will be saved to a file; commit this file to make future updates simpler.

Examples

This template is used by the following projects; have a look at what can be achieved!

Also, the following websites are build from the template: