What's new in 3.0

Wisej.NET 3 is the first release of Wisej that supports both .NET Framework (4.8) and .NET Core (now .NET 6, will be 7...) and runs on Windows, Linux and MacOS.

Wisej.NET 3 for .NET Framework 4.8 replaces Wisej 2.5 while Wisej.NET 3 for .NET Core 6 is a new build for ASP.NET Core applications that can run on Windows, Linux and MacOS.

Adding support for .NET Core and ASP.NET Core required changes to the HTTP/WebSocket layer and the implementation of a new Wisej Middleware module. Everything else is fundamentally unchanged or enhanced: .NET Controls, JavaScript Widgets, Designer, Themes, Theme Builder.

We have been using Wisej.NET 3 on Linux internally on many test projects and we have run it on several Linux distributions as well as small devices like the Raspberry Pi for over a year. And we are in the process of testing it on even smaller embedded devices running custom Linux builds.

Existing Wisej 2 applications should be able to run on Wisej.NET 3 mostly unchanged. Hopefully, all you need to update is the Visual Studio project format to the new "Sdk" project format.

The designer in Visual Studio is not available for the .NET Core targets and relies on the dual target approach, with the added benefit that Wisej applications can be deployed on .NET Framework 4.8 on Windows and .NET Core on Windows, Linux and MacOS.

The new Sdk project format has many properties that are not available in the project property panel. You'll have to get comfortable editing the .csproj or .vbproj files directly. See Project Properties for an example if the ones we added to our templates.

Multi Targeting

New projects can target multiple .NET platforms. When you create a new Wisej 3 project, our wizard will allow you to select the target and enable certain options:

You can edit the .csproj or .vbproj at any time and change the <TargetFrameworks> tag. Just make sure that "net48" is always the first, if you want to use the designer.

Multitargeting will inevitably require the use of conditional compilation and excluding certain source code files from some platforms. In our sources we use conditional properties and partial classes to neatly separate code keeping the same class structure.

.NET 6+ Windows, Linux, MacOS

Now that Wisej applications can run on .NET 6+ they are standard ASP.NET Core applications. When running on .NET 4.8 they are standard ASP.NET applications.

ASP.NET Core applications don't use System.Web anymore and are not integrated with the IIS pipeline. They are all based on OWIN and Microsoft's Kestrel. Which means that they are almost always self-hosted web applications.

Wisej supported the OWIN middleware approach since 1.5, based on Microsoft's Katana.

This is how you add Wisej to a standard ASP.NET Core application:

app.UseWisej();

All ASP.NET pages and all ASP.NET controls will NOT work on ASP.NET Core.

New Features

While the vast majority of the work in Wisej 3 has been to split the code between .NET Core and .NET Framework, replacing all of the ASP.NET code with ASP.NET Core, we have also added some cool new features.

Download Callback

All the Application.Download, and Application.DownloadAndOpen methods have a new optional argument: a callback function, invoked when the file has been downloaded by the client.

Application.Download(
    Application.MapPath("Docs\\Manual.pdf"), null, (fileName) => {
        AlertBox.Show($"Thank you for downloading {fileName}");
    }
);

// Or

Application.Download(
    Application.MapPath("Docs\\Manual.pdf"), null, this.OnDownloadManual);
);

private void OnDownloadManual(string fileName) {
    AlertBox.Show($"Thank you for downloading {fileName}");
}

This is a powerful new feature that allows an additional level of control that was not possible before.

Auto Layout

Wisej supports all sorts of very powerful layouts (impossible to achieve with any other web platform). However, all the layouts are implemented in layout engines and are "permanent": you have to set layout properties for the children and the container manages the layout using the specific layout engine.

If you just need to apply a specific layout, or a combination of layouts, by code, without having to change containers, use the Control.LayoutChildren() methods:

  • LayoutChildren(controls, dock, viewArea, spacing, useMargins, hAlign, vAlign)

  • LayoutChildren(controls, direction, viewArea, spacing, useMargins, hAlign, vAlign)

Each method is overloaded with multiple variants and most parameters are optional using predefined values.

Calling LayoutChildren without the controls collection and without the viewArea argument, arranges all the direct children using the control's DisplayRectangle as the bounding area.

Otherwise you can specify only a subset of the child controls and define a view area to limit the layout space. You can also try this new automatic layout functionality at design time using the new AutoLayout Panel.

You can call this method as many times as you need and with as many combinations of rules as you like. It doesn't change the layout engine or the layout options, it only moves and resizes the child controls according to the specified arguments.

Commanding

We have introduced a new experimental feature to extend the current data binding model to make it compatible with MAUI's Commanding approach. In our implementation, commands work seamlessly with the existing data binding and have access to the full context of the command source. In MAUI the command's code is limited to a single parameter.

In this first implementation, Button and SplitButton have a new Command property and CommandChanged event. The Command property can be data-bound to ICommand properties of the data source.

When ICommand.CanExecute returns false, the command source button automatically disables itself. Clicking the button invokes the method attached to the command.

Use the new Command class or Command<T> class to wrap the implementation of a command and to cast the data item coming from the data source.

public CommandDemoViewModel : BindingList<Person>
{
  public Wisej.Web.ICommand SaveCommand { get; }
  
  public CommandDemoViewModel()
  {
    this.SaveCommand = new Command<Person>(
      execute: args => {
        DB.Save(args.DataItem);
      },
      canExecute: args => {
        return  args.DataItem.Name != null &&
            args.DataItem.Age > 0;
      });
  }
}

This new feature is still experimental and may change in future builds.

New Interfaces

Added new interfaces that allow code to use common features across controls:

  • ILabel. Implemented by all controls that have the Label property.

  • IImage. Implemented by all controls that have the various Image (ImageIndex, ImageKey, ImageSource) properties.

  • IReadOnly. Implemented by all controls that have the ReadOnly property.

  • IModified. Implemented by all controls that have the Modified property.

Using these interfaces eliminates the need to cast a control to the specific class.

((ILabel)control).LabelText = "Name:";

// instead of

if (control is TextBox)
    ((TextBox)control).LabelText = "Name:";
else if (control is ComboBox)
    ((ComboBox)control).LabelText = "Name:";

Service Container

Wisej 3 adds a new experimental feature to support the dependency injection model natively.

The Application class now is an IServiceProvider and has two new methods to manage services: AddService and GetService with several overloads.

Using this new feature is quite simple, flexible and very powerful. Use Application.AddService() to register a service and Application.GetService() to retrieve it. Services can be added with several scopes:

  • Global. Only one instance (singleton) is shared among all sessions.

  • Session. Each session gets its own instance.

  • Transient. Each request gets a new instance.

When the services instance passed to AddService is null, Wisej will automatically try to instantiate the service class on first use (on demand). As soon as the service goes out of scope, Wisej will automatically dispose of it. If the service implements the IDisposable interface, it gets a call to IDisposable.Dispose()

A service can also be instantiated on demand in a callback.

Using the service is also quite simple. Regardless of the scope, the service consumer simply calls:

var service  = Application.GetService<ISaveService>();

// or

var service = (ISaveService)Application.GetService(typeof(ISaveService);

The return is just null if the requested service is unavailable.

DataGridView.DataRead

Our DataGridView control can handle an unlimited number of rows thanks to its built-in virtual scrolling and page caching on the client side. As the user scrolls the rows, the control manages a client-side cache of pages or rows and requests from the server only the pages that are needed to render the current view.

You can control the size of the client-side cache using the BlockSize and MaxCachedBlocks properties. When the DataGridView is in VirtualMode, it doesn't hold any data and can manage any number of rows with minimal memory usage. Your code provides the data as needed handling the CellValueNeeded and CellValuePushed events.

However, the VirtualMode events are fired every single time the application code uses a cell in the grid (in VirtualMode the grid doesn't hold any data). Usually, implementations of a virtual DataGridView must implement some kind of cache management on the server.

The new DataRead event makes this task a lot easier. It is fired when the client requests a page allowing an application to build and manage a small server-side cache in sync with the client scrolling, resulting in a much simpler usage of the VirtualMode events.

private void dataGridView_DataRead(object sender, DataGridViewDataReadEventArgs e)
{
    LoadRowsToCache(e.FirstIndex, e.LastIndex);
}

private void dataGridView_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
{
    e.Value = GetValueFromCache(e.ColumnIndex, e.RowIndex);
}

DataGridView.NoDataMessage

This property has been available as an experimental feature for a while and it's now a supported property. It takes an HTML string and it displays filling the entire grid space when there grid contains no rows.

Shows like this when the grid is empty.

General Improvements

  • Rolled up all bug fixes.

  • Layout speed improvements on the client and server.

  • Refreshed all icons in designer and Theme Builder.

Last updated