Visual Studio 2019

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.

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

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)