A Multilevel Responsive Blazor-menu

When you generate a Blazor-server app or Webassembly the standard template generates a menu docked to the left which is responsive, but has some drawbacks :

  • It doesn’t support multiple levels
  • It depends on bootstrap which introduces additional complexity
  • Menu-items are hardcoded

Webapplications need a flexible way enabling multilevel-, responsive menus, the standard template is not flexible enough for this purpose, this post describes a solution to this problem. The approach taken consists of the following parts :

  1. A Component ‘ResponsiveMenuComponent’
  2. A stylesheet ‘menu.css’
  3. JQuery functions to toggle menus

Suppose our requirement is to generate a menu whos structure comes from an external source and simulates a webshop, the menustructure looks like this :

Home
Jeans -> Wide leg jeans
Jeans -> Straight jeans
Jeans -> Loose jeans
Shorts -> Sweet jersey shorts -> Jersey1
Shorts -> Sweet jersey shorts -> Jersey2
Shorts -> Denim shorts
Skirts
Blazors

The ResponsiveMenu-component has a property ‘MenuEntryCollection’ which is used to render the menu. With the ResponsiveMenu-component in place, the following creates the required menu-structure :

MainLayout.razor :

<ResponsiveMenuComponent Items=@Items />

@code {
public MenuEntryCollection	Items = new MenuEntryCollection( )
	{
	new MenuEntry( ) { Text = "Home",		Url = "/" }
,	new MenuEntry( ) { Text = "Jeans",		Url = "/jeans" 
	, SubItems = new MenuEntryCollection( )
		{
		new MenuEntry( ) { Text = "Wide leg jeans",	Url = "/wide_leg_jeans" }
	,	new MenuEntry( ) { Text = "Straight jeans",	Url = "/straight_jeans" }
	,	new MenuEntry( ) { Text = "Loose jeans",	Url = "/loose_jeans" }
}
}
,	new MenuEntry( ) { Text = "Shorts",		Url = "/shorts" 
	, SubItems = new MenuEntryCollection( )
		{
		new MenuEntry( ) { Text = "Sweet jersey shorts",	Url = "/sweet_jersey" 
		, SubItems = new MenuEntryCollection( )
			{
			new MenuEntry( ) { Text = "Jersey1",	Url = "/Jersey1" }
		,	new MenuEntry( ) { Text = "Jersey2",	Url = "/Jersey2" }
	}
	}
	,	new MenuEntry( ) { Text = "Denim shorts",			Url = "/demin_jersey" 	}
}
}
,	new MenuEntry( ) { Text = "Skirts",		Url = "/skirts" }
,	new MenuEntry( ) { Text = "Blazers",	Url = "/blazers" }
};
}

The ‘MenuEntryCollection’ and ‘MenuEntry’-classes:

public class MenuEntryCollection : System.Collections.Generic.List<MenuEntry>{ }

public class MenuEntry
{
	internal string Text { get; set; }
	internal string Url  { get; set; }

	internal MenuEntryCollection SubItems{ get; set; }
}

When the website is rendered for non-mobile devices, multilevel-items are expanded by css included in ‘menu.css’. Before blazor, if a user would select an item in the menu a postback would occur rebuilding the menu, in Blazor this is not the case causing the selected menu-item to be expanded. In a regular blazor app, it is collapsed using css switched when a user clicks a div, the ‘@onclick=”ToggleNavMenu’ construct. Doing this for a menu whos structure is unknown in advance would be very convenient, therefore this is done using jQuery declared inside _Host.cshtml together with some other needed functions:

<script type="text/javascript">
window.onBlazorReady = function() {
	$('.top-menu').not(".mobile").click(function () {
		$(this).find('ul > li').toggle();
	});

	$('.top-menu').not(".mobile").hover(function () {
		$(this).find('ul > li').show();
	}, function() {});

	$('.menu-toggle').click(function () {
		$(this).siblings('.top-menu.mobile').slideToggle('slow');
	});

	$('.top-menu.mobile .sublist-toggle').click(function () {
		$(this).siblings('.sublist').slideToggle('slow');
	});

	$('.top-menu.mobile .sf-with-ul').click(function () {
		$(this).siblings('.sublist').slideToggle('slow');
	});

	$('.top-menu.mobile li:not(:has(ul))').click(function () {
		$(this).closest('.top-menu.mobile').toggle();
	});
};
</script>

The ‘onBlazorReady’-function is called inside the ‘OnAfterRenderAsync’ inside ‘ResponsiveMenuComponent.razor’:

protected override Task OnAfterRenderAsync( bool firstRender )
{
  if( firstRender)
  {
    ValueTask valueTask = JsRuntime.InvokeVoidAsync("onBlazorReady");
  }

  return base.OnAfterRenderAsync( firstRender );
}

A running website using this component can be seen here, sources can be downloaded from github.

Notes:

  • The menu in component ResponsiveMenuComponent is generated in codebehind which disables css-isolation
  • The example given is Blazor-server but can also be used for Blazor-WASM
  • Menu items for the desktop are collapsed on hover using css, but there is no such thing as ‘hover’ on mobile devices. Because of this, we need two separate menus mobile and desktop
Posted in .Net Core, Blazor, Visual Studio 2022, WASM | Leave a comment

Invoking Oracle Spatial from a Custom Command

Consider a requirement where the centroid of a set of points needs to be retrieved within a custom command. This can be achieved using the ‘GTDataProvider.DataContext.Execute‘-method:

IGTApplication GTApp = GTClassFactory.Create<IGTApplication>( );

string coors = "0, 0, 0, 10, 0, 0, 10, 10, 0, 0, 10, 0, 0, 0, 0"; // change to actual coordinates
string sql = $@"WITH shape AS
  ( 
  	select 1 id, sdo_geometry( 3003, NULL, NULL
         , sdo_elem_info_array( 1, 1003, 1)
         , sdo_ordinate_array({coors} )) G FROM dual
  )
SELECT s.ID, t.x, t.y FROM shape S, sdo_util.getvertices( sdo_geom.sdo_centroid( S.G)) t";

ADODB.Recordset rs = GTApp.DataContext.Execute( sql, out _, (int)ADODB.CommandTypeEnum.adCmdText);

if( rs == null) return;
if( rs.RecordCount == 0 ) return;
rs.MoveFirst( );

string x = System.Convert.ToString( rs.Fields[ "X"].Value);
string y = System.Convert.ToString( rs.Fields[ "Y"].Value);

System.Diagnostics.Debug.WriteLine( string.Format( "x={0}, y={1}", x, y)); // x=5, y=5

rs.Close();
rs = null;
Posted in Custom Commands, G/Technology, Intergraph, Oracle Spatial | Leave a comment

Rubberbanding in G/Technology

Whenever a user is digitizing a polygon, he expects feedback when moving the cursor before actually adding a point, the system should ‘rubberband’ the position under the cursor and only add it to a collection of points after the user left-mouse clicks in the Window. Key to rubberbending in G/Technology is responding to three events :

  1. CustomCommandHelper_Click
  2. CustomCommandHelper_MouseMove
  3. CustomCommandHelper_DblClick

CustomCommandHelper_Click

private void CustomCommandHelper_Click( object sender, GTMouseEventArgs e )
{
  if( this.PntCnt == 0)
  {
    this.PntCnt++;

    this.Idx = this.GeometryCreationService.AddGeometry( GTClassFactory.Create<IGTPolygonGeometry>(), StyleId);
    this.GeometryCreationService.AppendPoint( this.Idx, e.WorldPoint);
    return;
  }

  this.GeometryCreationService.AppendPoint( this.Idx, e.WorldPoint);
}

CustomCommandHelper_MouseMove

private void CustomCommandHelper_MouseMove( object sender, GTMouseEventArgs e )
{
  if( this.PntCnt == 0)
  {
    this.setGtStatusBar( "Click to start digitizing");
    return;
  }

  if( this.PntCnt == 1)
  {
    this.setGtStatusBar( "Click to add point");
    this.GeometryCreationService.SetDynamicPoint( this.Idx, e.WorldPoint);
    return;
  }
				
  this.setGtStatusBar( "Click to add point, Doubleclick to end");
  this.GeometryCreationService.SetDynamicPoint( this.Idx, e.WorldPoint);
}

CustomCommandHelper_DblClick

private void CustomCommandHelper_DblClick( object sender, GTMouseEventArgs e )
{
  this.PntCnt = 0;
}

Besides these 3 events, the following general code is used:

general code

private IGTApplication _GTApp = null;
private IGTApplication GTApp
{
  get
  {
    if( this._GTApp == null)
    {
      this._GTApp = GTClassFactory.Create<IGTApplication>();
    }

    return( this._GTApp);
   }
}

private IGTGeometryCreationService GeometryCreationService { get; set; } = GTClassFactory.Create<IGTGeometryCreationService>();

private void setGtStatusBar( string message)
{
  this.GTApp.SetStatusBarText( GTStatusPanelConstants.gtaspcMessage, message);
}

private int Idx { get; set; }
private const int StyleId = 5010;

The complete code can be downloaded from here.

Posted in Custom Commands, G/Technology, Intergraph, Visual Studio 2019 | Leave a comment

Specialized ViewComponents

Suppose you want to introduce an inheritance chain in ASP.Net Core to use some businesslogic in a baseclass :

namespace Foo.Core.Web
{
	using Microsoft.AspNetCore.Mvc;
	using System.Collections.Generic;

	public class CoolBaseViewComponentDetail : ViewComponent
	{
		protected List<string> getItems(int itemCount)
		{
			List<string> items = new List<string>();
			for (int i = 0; i < itemCount; i++)
			{
			  items.Add(string.Format("string {0}", i));
			}

			return (items);
		}
	}
 
	public class SpecializedViewComponentDetail : CoolBaseViewComponentDetail
	{
		public virtual IViewComponentResult Invoke(int numberOfItems)
		{
			List<string> items = getItems(numberOfItems);
			return (View(viewName: "~/Src/ViewComponentSpecialized.Detail.cshtml", model: items));
		}
	}
}

ViewComponentSpecialized.Detail renders like this :

@model System.Collections.Generic.List<string>
<ul>
    @foreach( string item in Model)
    {
        <li>@item</li>
    }
</ul>

If you invoke this viewcomponent you get an exception :

@await Component.InvokeAsync( name:"SpecializedViewComponentDetail", arguments:new { numberOfItems = 3 } )

InvalidOperationException: Could not find an 'Invoke' or 'InvokeAsync' method for the view component 'Foo.Core.Web.CoolBaseViewComponentDetail'.

ASP.Net core requires an ‘Invoke’-method on every ViewComponent-specialized class, even if it is not directly called. Solution is to introduce this method but to let it throw an exception when directly called:

namespace Foo.Core.Web
{
	using Microsoft.AspNetCore.Mvc;
	using System.Collections.Generic;
		
	public class CoolBaseViewComponentDetail : ViewComponent
	{
		public virtual IViewComponentResult Invoke(int numberOfItems)
		{
			throw new System.NotSupportedException("Specialized classes should implement this");
		}

		protected List<string> getItems(int itemCount)
		{
			List<string> items = new List<string>();
			for (int i = 0; i < itemCount; i++)
			{
			  items.Add(string.Format("string {0}", i));
			}

			return (items);
		}
	}
 
	public class SpecializedViewComponentDetail : CoolBaseViewComponentDetail
	{
		public virtual IViewComponentResult Invoke(int numberOfItems)
		{
			List<string> items = getItems(numberOfItems);
			return (View(viewName: "~/Src/ViewComponentSpecialized.Detail.cshtml", model: items));
		}
	}
}

Now the view renders:

o string 0
o string 1
o string 2

cheers, Stephan

Posted in .Net Core, ViewComponents, Visual Studio 2019 | Tagged , , , | Leave a comment

Configuring Webservice endpoints in a .Net Core Webapplication

If you need to invoke a WCF-Soap compliant Webservice from an ASP.Net application, the endpoints for it are stored in the Web.Config:

<client>
    <bindings>
      <basicHttpBinding>
        <binding name="BasicHttpBinding_IService1" />
        <binding name="BasicHttpsBinding_IService1">
          <security mode="Transport" />
        </binding>
        <binding name="BasicHttpBinding_IService11" />
        <binding name="BasicHttpsBinding_IService11">
          <security mode="Transport" />
        </binding>
      </basicHttpBinding>
    </bindings>

  <endpoint address="http://sdeservice.azurewebsites.net/Service1.svc"
            binding="basicHttpBinding"
            bindingConfiguration="BasicHttpBinding_IService1"
            contract="ServiceReference1.IService1" 
            name="BasicHttpBinding_IService1" />

  <endpoint address="https://sdeservice.azurewebsites.net/Service1.svc"
            binding="basicHttpBinding"
            bindingConfiguration="BasicHttpsBinding_IService11"
            contract="ServiceReference2.IService1" 
            name="BasicHttpsBinding_IService1" />
/client>

If you need to invoke the same Webservice from an ASP.Net Core application, there is no Web.Config and you need to store the endpoints in an applicationSettings.json file. The following applicationSettings.json file introduces a section 'Service1' with some properties defining the Webservice to use:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "Service1": {
    "Title": "Service1",
    "Name": "Very Cool Webservice",
    "Endpoint": "http://sdeservice.azurewebsites.net/Service1.svc",
    "SecureEndpoint": "https://sdeservice.azurewebsites.net/Service1.svc"
  },
  "AllowedHosts": "*"
}

There are a couple of ways to get this configuration in .Net Core 3.1 :

  1. Bind configuration in controller
  2. Bind configuration in Startup
  3. Dynamically read configuration

Bind configuration in controller

Any .Net Core application allows you to bind a configuration to a simple POCO-class, the following code binds an instance of class 'Service1Config' to properties 'Title', 'Name', 'Endpoint' and 'SecureEndpoint' from the 'Service1'-section. Your controller needs to have a constructor with an 'IConfiguration'-argument, ASP.Net Core will invoke it and you can use it to bind to a class. Then the endpoint as stored in the 'Endpoint'-attribute can be used at the endpoint of a Webservice-client as shown in the ‘Invoke’-method or ‘InvokeSecure’ to use https:

public class HomeController : Controller
{
  private Service1Config ConfigClass { get; set; }

  public HomeController( IConfiguration configuration)
  {
    this.ConfigClass = new Service1Config( );
    IConfigurationSection section = configuration.GetSection( "Service1");
    section.Bind( this.ConfigClass);
  }

  public IActionResult Invoke()
  {
    ServiceReference1.Service1Client client = new ServiceReference1.Service1Client( Service1Client.EndpointConfiguration.BasicHttpBinding_IService1);
    string endpoint = this.ConfigClass.Endpoint;
    client.Endpoint.Address = new EndpointAddress( endpoint);
    string result = this.Client.GetData( 3);
    return( View( ));
  }

  public IActionResult InvokeSecure()
  {
    ServiceReference1.Service1Client secureClient = new ServiceReference1.Service1Client( Service1Client.EndpointConfiguration.BasicHttpsBinding_IService1);
    string secureEndpoint = this.ConfigClass.SecureEndpoint;
    secureClient.Endpoint.Address = new EndpointAddress( secureEndpoint);
    string result = this.Client.GetData( 3);
    return( View( ));
  }
}

public class Service1Config
{
  public string Title		  { get; set; }
  public string Name		  { get; set; }
  public string Endpoint	  { get; set; }
  public string SecureEndpoint{ get; set; }
}

Bind configuration in Startup

When you need to have access to the configuration globally, you can do the same in your programs 'Startup.cs':

public class Startup
{
  internal Service1Config ConfigClass{ get; set; }

  public Startup( IConfiguration configuration)
  {
    this.ConfigClass= new Service1Config( );
    IConfigurationSection section = configuration.GetSection( "Service1");
    section.Bind( this.ConfigClass);
  }
}

Dynamically read configuration

If you need to dynamically read the same configuration, you can do so like this :

public class HomeController : Controller
{
  public IActionResult Index()
  {		
    string title = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build().GetSection("Service1")[ "Title"];
    string title = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build().GetSection("Service1")[ "Title"];
    string endpoint = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build().GetSection("Service1")[ "Endpoint"];
    string secureEndpoint = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build().GetSection("Service1")[ "SecureEndpoint"];
    return( View( ));
  }
}

Notes

  • If you add a reference to a Webservice, Visual Studio 2019 will add a 'connectedServices.json' file which also appears when you publish suggesting it stores a reference to the endpoint url used by a webservice-client. This is not the case, and the file can be safely removed from the publish.
  • If you generate a proxy for http, the generated proxy will contain a constructor without arguments
  • If you generate a proxy for http and https (examples are using that), the generated proxy will contain a constructor with an argument indicating the protocol to use, either http or https (BasicHttpBinding_IService1/BasicHttpsBinding_IService1)
Posted in .Net Core, Visual Studio 2019, WebService | Tagged , | Leave a comment