Visual Studio – Xamarin – Traffic Light

Background

Here we go with our first small CSC 101 application in Xamarin.

Outline

  1. addControls
    • instantiate Button Object
    • If in manual mode
      • Add event handler for Button Click
    • Instantiate StackLayout
      • Add Button
  2. OnButtonClick
    • handle buttonclick event
      • call triggerNextState
  3. triggerNextState
    • Call Device.BeginInvokeOnMainThread
      • Have next method be processed on main thread
  4. reflectState
    • Update Button Text
    • Base next procession based on whether manual or automated
      • Enable Button
      • Update Button Background Color
  5. initializeData
    • Instantiate each trafficLightState
    • Add trafficLightState object to collection list
  6. initializeTimer
    • Instantiate timer
    • instantiate Time Event Handler
  7. Process Timer Event
    • call triggerNextState

Code

Add Controls


 void addControls()
{

	btnTL = new Button
	{

		  Text = btnLTText + " " + strMode
		, HorizontalOptions = LayoutOptions.StartAndExpand

	};

	if (bManualOrAutomated == false)
	{
		btnTL.Clicked += OnButtonClicked_btnTL;

	}

	// Assemble the page.
	this.Content = new StackLayout
	{

		Children =
		{
			new StackLayout
			{

				  // Stack Orientation
				  Orientation = StackOrientation.Horizontal

				  // Add Children
				, Children =
				{
					btnTL
				}

			 } // Stack Layout
		} // Children

	}; // Assemble the page.

} // addControls() 

OnButtonClicked_btnTL


void OnButtonClicked_btnTL(object sender, EventArgs args)
{

	Button button = (Button)sender;

	if (button == btnTL)
	{

		triggerNextState();

	}

}

triggerNextState


void triggerNextState()
{

	//prepare for next transition
	iTrafficLightStateCurrent = iTrafficLightStateCurrent + 1;

	try
	{
		Device.BeginInvokeOnMainThread
			(
				() =>
				{
					reflectState();
				}
			);
	}
	catch (Exception)
	{

	}

}

reflectState


void reflectState()
{

	try
	{

		//reset display
		//btnTL.Text = btnLTText;
		btnTL.Text = btnLTText + " " + strMode;

		if (dtSignalTime != null)
		{
			btnTL.Text += " ("
							+ dtSignalTime.ToLongTimeString().TrimStart()
							+ " )"
							;
		}

		btnTL.IsEnabled = (bManualOrAutomated == false);

		//reset marker if at end of line
		if (
				(iTrafficLightStateCurrent > iTrafficLightStatesCountZeroBased)
		   )
		{
			iTrafficLightStateCurrent = 0;
		}

		// get object at current node
		objTLSCurrent = objTLS[iTrafficLightStateCurrent];

		// if valid object
		if (objTLSCurrent != null)
		{

			//set background color
			btnTL.BackgroundColor = objTLSCurrent.color;

		}

	}
	catch (Exception ex)
	{

		//show exception
		btnTL.Text = ex.Message;

		//reset counter
		iTrafficLightStateCurrent = 0;

	}
}

initializeData


void initializeData()
{

	tlStateRed = new trafficLightState();
	tlStateRed.color = Color.Red;
	objTLS.Add(tlStateRed);

	tlStateGreen = new trafficLightState();
	tlStateGreen.color = Color.Green;
	objTLS.Add(tlStateGreen);

	tlStateYellow = new trafficLightState();
	tlStateYellow.color = Color.Yellow;
	objTLS.Add(tlStateYellow);

	// Get Number of Elements
	iTrafficLightStatesCount = objTLS.Count;

	// Get Number of Elements ( 0 Based )
	iTrafficLightStatesCountZeroBased = objTLS.Count - 1;

	// Set Current at 0
	iTrafficLightStateCurrent = 0;

}

initializeTimer


void initializeTimer(Boolean bTimer)
{

	if (bTimer)
	{

		// Create a timer with a two second interval.
		objTimer = new System.Timers.Timer(lSecondsInThousands);

		// Hook up the Elapsed event for the timer.
		objTimer.Elapsed += OnTimedEvent;

	}
	else
	{

		if (objTimer != null)
		{

			//unsubscribe
			objTimer.Elapsed -= OnTimedEvent;

			objTimer = null;

		}

	}

	if (objTimer != null)
	{

		objTimer.AutoReset = bTimer;

		objTimer.Enabled = bTimer;

	}

}

OnTimedEvent


private void OnTimedEvent(Object source, ElapsedEventArgs e)
{

    dtSignalTime = e.SignalTime;

    triggerNextState();

}

Source Code Control

GitHub

VS Studio Xamarin Traffic Light
Link

References

  1. Microsoft
    • Microsoft | Docs
      • Docs / .NET / .NET API Browser / Xamarin.Forms / Device / Methods / BeginInvokeOnMainThread
        Link

 

.Net Frameworks – Installed Products

Background

Wanted to get a list of Microsoft .Net Frameworks installed on a machine.

Windows Management Interface ( WMI )

Introduction

In this post, we will use Windows Management Interface, WMI.

 

Powershell

Outline

  1. Get-WmiObject
    • Class :- Win32_Product
    • Filter :- Microsoft .Net Framework
    • Columns :-
      • Name
      • Version

Code


[char] $CHAR_WILDCARD="*"

$computer=$env:computername

$filter="Microsoft .Net Framework"

$filterWildcard= $filter + $CHAR_WILDCARD

Get-WmiObject -Class Win32_Product -Computer $computer | `

	Select @{"Name"="Name"; "Expression"={($_.Name.trim())}}, Version | `

	Where-Object -FilterScript {$_.Name -like $filterWildcard} | `

	Sort-Object Name, Version -Descending

Images

getVersionNumber_self_20181207_1209PM

Powershell

Outline

  1. Get-WmiObject
    • Class :- Win32_Product
    • Filter :- Microsoft .Net Framework
    • Columns :-
      • Name
      • Version

Code


set "_wildcard=%%"

wmic product where "Name like 'Microsoft .Net %_wildcard%' " get Name, Version

Images

MSWindowsBatch.20181207.1229PM

Visual Studio – Error – “Could not load file or assembly ‘netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51’ or one of its dependencies.”

Background

Upon preparing Visual Studio to be able to develop mobile applications courtesy of Xamarin, ran into a blocking issue right away.

Set the Stage

Setting the stage for our error is easy  and it goes :-

  1. Launch Visual Studio
  2. Elected to design new Project
  3. Chose Project based on Mobile / Xamarin
  4. The system prepares the baseline project
  5. Attempted to build the project

Error

Error Image

CouldNotloadfileorassemblynetstandard.20181207.0340AM.PNG

Error Text


Could not load file or assembly 'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' or one of its dependencies.

Troubleshooting

Microsoft .Net Version

Review the version of Microsoft .Net that is installed.

Windows Explorer

Launched Windows Explorer and entered %windir%\Windows\Microsoft.NET\Framework64 as targeted folder.

Image

WindowsExplorer.20181207.0401AM

Explanation

We have a Version 4 version of Microsoft .Net installed; The baseline version # is v4.0.30319.

Please keep in mind that patches to his baseline might be installed, as well.

 

Registered Files

Registered File – netstandard.dll

Accessed %windir%\Windows\Microsoft.NET\Framework64  to determine if the referenced Assembly, netstandard.dll is present.

Confirmed it is not.

Remediation

Microsoft .Net Version

Goal

Installing .net framework 4.7.1 and above will help!

Artifacts

Availability

Please access https://dotnet.microsoft.com/download ( Link ) and see if there are newer versions of .Net than the one you have installed.

As of this morning, 2018-12-07, here is what is available.

Image

DotNetArtifacts.20181207.0826AM

Explanation

The latest .Net Framework, not the .Net Core, is 4.7.2.

 

Product :- v4.7.2

Image

DotNetArtifacts.4.7.2.20181207.0832AM.PNG

Explanation
  1. .Net Framework 4.7.2
    • Products
      • Build apps – Dev pack
      • Run apps – Runtime

We chose the Developer pack ( offline installer ).

Installer

Install

Images
License Terms

licenseTerms.20181207.0417AM.PNG

Setup Successful

setupSuccessfully.20181207.0426.PNG

 

Restarted Machine

Restarted Machine

Validation

Assemblies

Assembly – netstandard.dll

Windows Explorer

Accessed Windows Explorer and targeted C:\Windows\Microsoft.NET\Framework64\v4.0.30319

Steps

Took the following steps :-

  1. Folder View
    • Columns
      • Added Date Created
    • Sort/Order
      • Ordering by Date Created
Image

WindowsExplorer.20181207.0843AM.PNG

Explanation
  1. netstandard.dll now included

 

Visual Studio

Project Build

Launched Visual Studio, loaded our mobile project, and it compiled successful with a couple of non-blocking warnings.

Image

newProject.errorList.noerrors.20181207.0516AM.PNG

Explanation

  1. As we do not have a Mac attached expected this warning.

 

Referenced Work

  1. xamarin
    • xamarin/Xamarin.Forms
      • Xamarin.Forms.Build.Tasks.GetTasksAbi build error #2667
        Link
    • forums.xamarin.com
  2. David Kean
    • Twitter ( @davkean )
      • Folks this is a bug in the Xamarin task – it’s making the assumption netstandard is in the GAC.
        Link

.Net / Error “log4net:ERROR [AdoNetAppender] Failed in DoAppend System.TypeInitializationException: The type initializer for ‘log4net.GlobalContext’ threw an exception”

Background

Ran into a nice easy to fix error.

Error

Here is the error:

Image

Text


-- More  -- log4net:ERROR [AdoNetAppender] Failed in DoAppend
System.TypeInitializationException: The type initializer for 'log4net.GlobalContext' threw an exception. ---> System.Con
figuration.ConfigurationErrorsException: Configuration system failed to initialize ---> System.Configuration.Configurati
onErrorsException: Only one <configSections> element allowed per config file and if present must be the first child of the root <configuration> element. 
(hello.exe.Config line 11)
   at System.Configuration.ConfigurationSchemaErrors.ThrowIfErrors(Boolean ignoreLocal)
   at System.Configuration.BaseConfigurationRecord.ThrowIfParseErrors(ConfigurationSchemaErrors schemaErrors)
   at System.Configuration.ClientConfigurationSystem.EnsureInit(String configKey)
   --- End of inner exception stack trace ---
   at System.Configuration.ConfigurationManager.PrepareConfigSystem()
   at System.Configuration.ConfigurationManager.GetSection(String sectionName)
   at System.Configuration.PrivilegedConfigurationManager.GetSection(String sectionName)
   at System.Net.Configuration.SettingsSectionInternal.get_Section()
   at System.Net.Sockets.Socket.InitializeSockets()
   at System.Net.Dns.GetHostName()
   at log4net.Util.SystemInfo.get_HostName()
   at log4net.GlobalContext..cctor()
   --- End of inner exception stack trace ---
   at log4net.GlobalContext.get_Properties()
   at log4net.Core.LoggingEvent.CreateCompositeProperties()
   at log4net.Core.LoggingEvent.CacheProperties()
   at log4net.Core.LoggingEvent.FixVolatileData(FixFlags flags)
   at log4net.Appender.BufferingAppenderSkeleton.Append(LoggingEvent loggingEvent)
   at log4net.Appender.AppenderSkeleton.DoAppend(LoggingEvent loggingEvent)



 

TroubleShooting / Remediation

Error Message

The key data from our error message is highlighted below:

System.TypeInitializationException: The type initializer for ‘log4net.GlobalContext’ threw an exception. —>
System.Configuration.ConfigurationErrorsException: Configuration system failed to initialize —> System.Configuration.ConfigurationErrorsException:
Only one <configSections> element allowed per config file and if present must be the first child of the root <configuration> element.
(hello.exe.Config line 11)

 

Configuration File

[app].config

Original


<?xml version="1.0" encoding="utf-8" ?>
<configuration>

    <appSettings>
        <add key="fileDebug" value="debug" />
    </appSettings>
  
    <configSections>
        

<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
    </configSections>
    
    <log4net debug="false">
       
        <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
            <layout type="log4net.Layout.PatternLayout">
                <conversionPattern value="%message%newline" />
            </layout>
        </appender>

        ....
        ....

Revision


<?xml version="1.0" encoding="utf-8" ?>
<configuration>

    <!--
        <appSettings>
            <add key="fileDebug" value="debug" />
        </appSettings>
    -->  
    
    <configSections>
        
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
    </configSections>
    
    <log4net debug="false">
    

        <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
            <layout type="log4net.Layout.PatternLayout">
                <conversionPattern value="%message%newline" />
            </layout>
        </appender>
               
         
    </log4net>

    <!-- moved here on 2018-02-13 by dadeniji --> 
    <appSettings>
        <add key="fileDebug" value="debug" />
    </appSettings>
    
</configuration>

 

Summary

Again, the fix is to make sure that nothing comes before the configSections element/section in our App’s configuration file ( *.exe.config).

Log4Net just happens to be the scapegoat here…

.Net – Raise the Debugger in the event of an error

Background

Many years ago a good friend showed my a programming trick he learnt from a Vendor.

The trick goes like this:

If a file is present, launch the debugger.

To offer an incline on how long people have been using this trick, I saw it on a computer running IBM OS/2.  And, the programming language was C.

Personally, there is a lot of things that I miss from server side, service level, programming.

It is good to see it is still workable in .Net

Sample Code

Source Code

debugMode.cs


using System;
using System.Text;
using System.IO;
using System.Configuration;
using System.Diagnostics;
  
class debugMode
{

  static string strFileDebug;
  static string PARAMETER_FILE_DEBUG = "fileDebug";      
  
  static Boolean bFileExists = false;
  
  static string strMessage;
  
  static void Main(string[] args)
  {

	debugMode objDebugMode;       
	
	   objDebugMode = new debugMode();
	   
			objDebugMode.parse();
		
			objDebugMode.processDebug();
		
	objDebugMode = null;
   
  }


  
  public debugMode()
  {
			 
  }

  
  
  public void parse()
  {
	  
	   getConfigurationData();
	   
	   bFileExists = File.Exists(strFileDebug);
	  
  }
	
  private void getConfigurationData()
  {
			  
	try
	{

		strFileDebug = ConfigurationManager.AppSettings[PARAMETER_FILE_DEBUG];
		
	   
	}
	catch(Exception objEx)   
	{
		
	   strMessage = "Exception occurred " + objEx.Message;
					
	   Console.WriteLine(strMessage);
	   
	   
	}                  
	 
  }
  
  
  public void processDebug()
  {
	  
	  if (bFileExists)
	  {
			// Start debugger
			System.Diagnostics.Debugger.Break();
			
	  }
	  
  }
  
		
}



makeDebugDemo.cmd


set "_compiler=C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\"

set "_FILE_SET= debugMode.cs "

set "_OPTION=/nologo /debug "

set "_ADDLIB_S= /r:System.dll"
set "_ADDLIB_SD= /r:System.Data.dll"
set "_ADDLIB_IO= /r:System.IO.dll"
set "_ADDLIB_WB= /r:C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\WPF\WindowsBase.dll"

set "_MAINCLASS=debugMode"

set "_APPNAME=debugDemo.exe"

set "_MAIN=/main:%_MAINCLASS%"

if exist %_APPNAME% del %_APPNAME%

%_compiler%\csc.exe /out:%_APPNAME%  %_OPTION% %_ADDLIB_L4N% %_ADDLIB_S% %_ADDLIB_SD% %_ADDLIB_IO% %_ADDLIB_WB% %_MAIN%  %_FILE_SET%

 

Configuration

debugDemo.exe.config

In the app.config file, please set the file that will trigger the debugger by setting fileDebug value.


<?xml version="1.0" encoding="utf-8" ?>
<configuration>

	<appSettings>
		<add key="fileDebug" value="debug" />
	</appSettings>
	
</configuration>	


Lab

Let us test things out:

  1. create marker file ( debug )
  2. Compile App
  3. Run app

 

Compile App

Command

makeDebugDemo.cmd

Output

Run App


debugDemo.exe

 

Run App

Image

…has stop working

 Visual Studio Just-In-Time Debugger

New Instance of Visual Studio 2010

New Instance of Visual Studio Community 2017

 

Microsoft Visual Studio ( Debugging )

.Net – LINQ – Exception – “Sequence contains no elements”

Background

Playing around a bit with LINQ / Entity Framework and running into exceptions.

Unfortunately the code itself is Vendor Supplied and reasonable big and so I am having to set up little test code snippets to have a playground.

With a playground we can try to see if we will be able to set up the right conditions that will “force” the error.

 

NoDB

To make things easy and self contained, we will not have an actual database.

We’ll just declare a simple class and create a few instance of the class.

Each instantiation will be added into a list.

Data

Athlete Team Position
Svetlana Omelchenko 98
Claire O’Donnell 75
Sven Mortensen 88
Cesar Garcia 65

 

Code

Class – Student



    public class Student  
    {  
	
        public string FirstName { get; set; }  
        public string LastName { get; set; }  
        public int Score { get; set; }  

		
    }  


 

Populate List


static ArrayList arrList = new ArrayList();  

static void populateList()
{
	
				
	arrList.Add
	(  
		new Student  
			{  
				  FirstName = "Svetlana"
				, LastName = "Omelchenko"
				, Score = 98  
			}
	);  
	
	arrList.Add
	(  
		new Student  
			{  
				  FirstName = "Claire"
				, LastName = "O’Donnell"
				, Score = 75
			}
	);  
	
	arrList.Add
	(  
		new Student  
			{  
				  FirstName = "Sven"
				, LastName = "Mortensen"
				, Score = 88
			}
	);  
	
	arrList.Add
	(  
		new Student  
			{  
				  FirstName = "Cesar"
				, LastName = "Garcia"
				, Score = 65
			}
	);  
	
}

 

Get Minimum Score to display


Console.Write("Please enter minimum score :- ");

String value = Console.ReadLine();

int iScore = -1;


try 
{
	
	iScore  = Int32.Parse(value); 
		
	Console.WriteLine("{0} --> {1}", value, iScore);
}
catch (FormatException) 
{
	Console.WriteLine("{0}: Bad Format", value);
}   

catch (OverflowException) 
{
	Console.WriteLine("{0}: Overflow", value);   

}  

 

Query List (arrList) filtering on mininal score


var query = from Student student in arrList  
	    orderby student.Score descending
            where student.Score > iScore
            select student
						;  

Simple Result Set Iteration


						
foreach (Student s in query) 
{			
   Console.WriteLine(s.LastName + ": " + s.Score);  
};  

Access Recordset


/*
	Get First Element in recordset
*/
try
{

	//get First element in set
	var varElement = query.First();

	if (varElement != null)
	{
		Console.Write("Lastname is :- " );
		Console.WriteLine(varElement.LastName);
	}

}
catch (Exception ex)
{

	Console.WriteLine("Error getting first element - query.First()");

	Console.WriteLine("Exception Message:- " + ex.Message);

	Console.WriteLine("StackTrace:- " + ex.StackTrace);

	Console.WriteLine(STRING_LINEBREAK);
}


/*
	Get Average Score in recordset
*/
try
{

	fltAverage = query.Average(e => e.Score);

	Console.Write("Average Score " );
	Console.WriteLine(fltAverage);


}
catch (Exception ex)
{

	Console.WriteLine("Error getting average score - query.Average(e => e.Score) ");

	Console.WriteLine("Exception Message:- " + ex.Message);

	Console.WriteLine("StackTrace:- " + ex.StackTrace);
				
	Console.WriteLine(STRING_LINEBREAK);

}					


Exception

  1. Exception Message:-
    • Sequence contains no elements
  2. Exception StackTrace :-
    • at System.Linq.Enumerable.First[TSource](IEnumerable`1 source)
    • at System.Linq.Enumerable.Average(IEnumerable`1 source)

Remediation

Plan

Our hope is that we will need a couple of helper methods.
The helper methods will check the recordset of a LINQ Query and compute counts, isEmpty, etc.

Extension Methods

Lasse Espeholt

Code provided by Lasse Espeholt here.

 

static class EnumerableExtensions
{
	public static int Count(this IEnumerable source)
	{
		int res = 0;

		foreach (var item in source)
		{
			res++;
		}
		
		return res;
	}
	
	public static Boolean isEmpty(this IEnumerable source)
	{
		Boolean bEmpty = true;

		foreach (var item in source)
		{
			bEmpty = false;
			break;
		}
		
		return (bEmpty);
	}
	
}

Validate data before Use

 

// Get number of Elements
iNumberofElements = query.Count();

//Is List Empty
bEmpty = query.isEmpty();

// If List is not empty	
if (bEmpty == false)
{
	
	Console.WriteLine("Count :- " + iNumberofElements);	

	//get First element of a Sequence
	var varElement = query.FirstOrDefault();

	//If first element is not null
	if (varElement != null)
	{
	
		Console.WriteLine("Lastname :- " + varElement.LastName);
	
	}	
	
	fltAverage = query.Average(e => e.Score);
	
	Console.WriteLine("Average Score :- " + fltAverage);
	
}	
else
{
	
	Console.Write("Empty recordset returned from searching for test scores greater than :- ");
	
	Console.WriteLine(iScore);
}		
	

 

Summary

There are a few safeguards that one can employ.  Example are listed below:

  1. Sequence.FirstOrDefault
  2. If Sequence.Any the Sequence.First
  3. Sanitize recordset using count or exists before aggregating data

 

References

  1. docs.microsoft.com
    •  Docs/NET / C# Guide / Programming guide / Programming concepts / LINQ
      • How to: Query an ArrayList with LINQ (C#)
        Link
  2. Data Developer Center
    • Performance Considerations for EF 4, 5, and 6
      Link
  3. Technical Overhead
    • Nathan
      • LINQ Single vs SingleOrDefault vs First vs FirstOrDefault
        Link
  4. DotNetPerls
    • FirstOrDefault
      Link
  5. Telerik
    • How to: Convert the Results of a LINQ Query to an Array
      Link
  6. Tech Crazy Zone
    • Gihan Lakmal
      • C# – Fix Sequence Contains no elements
        Link
  7. Stack Overflow
    • Calculating Count for IEnumerable (Non Generic)
      Link
    • Implicitly typed local variables must be initialized
      Link
    • Entity Framework 4 Single() vs First() vs FirstOrDefault()
      Link
    • Sequence contains no elements?
      Link

Entity Framework – Change Database Column – Error – “could not be set to a ‘System.Boolean’ value. You must set this property to a non-null value of type ‘System.String'”

 

Background

Upon changing one of our database columns from varchar to bit, we experienced an invalid “System.InvalidOperationException” issue.

 

Error

Error – “Unhandled Exception: System.InvalidOperationException: The ‘status’ property on ‘usp_TraceFlagSourceFetch_Result1’ could not be set to a ‘System.Boolean’ value.
You must set this property to a non-null value of type ‘System.String'”

Here is the error we are experiencing.

Error Text


Unhandled Exception: System.InvalidOperationException: The 'status' property on 'usp_TraceFlagSourceFetch_Result1' could not be set to a 'System.Boolean' value.
You must set this property to a non-null value of type 'System.String'.
at System.Data.Entity.Core.Common.Internal.Materialization.Shaper.ErrorHandlingValueReader`1.GetValue(DbDataReader reader, Int32 ordinal)
at lambda_method(Closure , Shaper )
at System.Data.Entity.Core.Common.Internal.Materialization.Coordinator`1.ReadNextElement(Shaper shaper)
at System.Data.Entity.Core.Common.Internal.Materialization.Shaper`1.SimpleEnumerator.MoveNext()
at lab001VS.UITraceFlag.listTraceFlagSourceUseSPImplicit(Int32 iID) in

TroubleShooting

We need to find “usp_TraceFlagSourceFetch_Result1

 

Model Browser

We found usp_TraceFlagSourceFetch_Result1 via the Model Browser.

Model Browser – Original

We looked in the Model Browser and this is what it looks like:

 

Model Browser – Original – Column – Status

Let us review the identified Property, Status.

 

Model Browser – Original – Column – Status – Type – “String”

Model Browser – Original – Column – Status – Type – “Boolean”

Please change the Type of Status from String to Boolean.

 

 

Summary

Really impressed with how quickly Entity Framework makes it to align .Net code with database structural changes.

This expediency especially when change can be localized to the Model and made via the Model Browser is intriguing.