ArcGIS in Asp.NET Blazor – No JavaScript Required! 

Post Updated to reflect the rebranding and latest version of GeoBlazor 12/10/22

In my previous post, I showed how to call the ArcGIS JavaScript API using the IJSRuntime from Asp.NET Blazor. Today, I’m happy to announce a free, open-source NuGet package for accessing ArcGIS directly in Blazor components, no JavaScript required!

To get started, create a new Blazor Server application, and add a PackageReference to the dymaptic.GeoBlazor.Core package (via your IDE’s Nuget Package Manager or dotnet add package dymaptic.GeoBlazor.Core).

Next, in Pages/_Layout.cshtml, add the following to the head block of the html.

				
					     <link href="_content/dymaptic.GeoBlazor.Core"/>
 <link href="_content/dymaptic.GeoBlazor.Core/assets/esri/themes/light/main.css" rel="stylesheet" />
				
			

Next, you will need to get an ArcGIS API Key from the ArcGIS Developers Dashboard. For security reasons, you should not add this key to any files that will be checked into version control. I recommend adding a User Secrets file. You can also use appsettings.development.json, but if you do, I recommend adding it to .gitignore before checking in your code.

Here’s what the JSON would look like in secrets.json or appsettings.development.json:

				
					{ 
  "ArcGISApiKey": "YOUR_API_KEY" 
} 
				
			
This will be picked up and added to your maps automatically by the Asp.NET dependency injection framework.

Next, let’s add the following @using statements to _Imports.razor so that our pages and components have access to the necessary package components.

				
					@using dymaptic.GeoBlazor.Core.Components
@using dymaptic.GeoBlazor.Core.Components.Geometries
@using dymaptic.GeoBlazor.Core.Components.Layers
@using dymaptic.GeoBlazor.Core.Components.Popups
@using dymaptic.GeoBlazor.Core.Components.Symbols
@using dymaptic.GeoBlazor.Core.Components.Views
@using dymaptic.GeoBlazor.Core.Objects
				
			

That’s it! Now you are ready to write your first ArcGIS Map View in Blazor. Add the following code to the bottom of Pages/Index.razor.

				
					<MapView Longitude="_longitude" Latitude="_latitude" Zoom="11" Style="height: 400px; width: 100%;"> 
    <Map ArcGISDefaultBasemap="arcgis-topographic"> 
        <GraphicsLayer> 
            <Graphic> 
                <Point Longitude="_longitude" Latitude="_latitude"/> 
                <SimpleMarkerSymbol Color="@(new MapColor(226, 119, 40))"> 
                    <Outline Color="@(new MapColor(255, 255, 255))" Width="1"/> 
                </SimpleMarkerSymbol> 
            </Graphic> 
            <Graphic> 
                <PolyLine Paths="@(new[] { _mapPath })" /> 
                <SimpleLineSymbol Color="@(new MapColor(226, 119, 40))" Width="2"/> 
            </Graphic> 
            <Graphic> 
                <Polygon Rings="@(new[] { _mapRings })" /> 
                <SimpleFillSymbol Color="@(new MapColor(227, 139, 79, 0.8))"> 
                    <Outline Color="@(new MapColor(255, 255, 255))" Width="1"/> 
                </SimpleFillSymbol> 
                <Attributes Name="This is a Title" Description="And a Description"/> 
                <PopupTemplate Title="{Name}" StringContent="{Description}"/> 
            </Graphic> 
        </GraphicsLayer> 
    </Map> 
</MapView> 
				
			
				
					@code 
{ 
    private readonly double _latitude = 34.027; 
    private readonly double _longitude = -118.805;
    
    private readonly MapPath _mapPath = new(new MapPoint(-118.821527826096, 34.0139576938577), 
        new MapPoint(-118.814893761649, 34.0080602407843), 
        new MapPoint(-118.808878330345, 34.0016642996246)); 
        
    private readonly MapPath _mapRings = new(new MapPoint(-118.818984489994, 34.0137559967283), 
        new MapPoint(-118.806796597377, 34.0215816298725), 
        new MapPoint(-118.791432890735, 34.0163883241613), 
        new MapPoint(-118.79596686535, 34.008564864635), 
        new MapPoint(-118.808558110679, 34.0035027131376)); 
} 
				
			

Run the project, and you should see your new map directly in the Blazor application. Congratulations! You have added ArcGIS in Asp.NET Blazor without writing a single line of JavaScript. 

If you’d like to see the API in action and check out our dozens of samples, visit our Samples Page, where we have hosted a full application built on top of the open-source project.

To learn more about the dymaptic Blazor GIS API, visit the code repository at https://github.com/dymaptic/dy-blazor-api-core. Download the repository and test out the Samples applications, which include all of the following and more:

  • Sample applications for Blazor Server, Blazor Wasm, and Blazor Maui (multi-platform, including Android, iOS, Windows, and MacOS)
  • Demonstrating loading maps and feature layer data from ArcGIS Online
  • Navigation
  • Graphic Drawing
  • Renderers
  • Popups
  • Widgets
  • SQL Data Queries
  • Routing

Are there any other features you would like to see? Do you have suggestions? Questions? Want help building your Blazor GIS application? We’d love to hear from you! Send us a message and let’s get the conversation started. Visit GeoBlazor.com for more information.

14 thoughts on “ArcGIS in Asp.NET Blazor – No JavaScript Required! ”

  1. First of all; great work! I’ve done some testing with the WASM sample project. There seems to be no sample on how to use the spatial reference component. I’m using a data source (REST API) that spits out coordinates in form of ETRS-TM35FIN (EPSG:3067) (i think the correct wkid is 104129?) and they are naturally way off when trying to plot them to a WGS84 based map. From arcgis js developer documentation i’ve understood that i should use the spatial reference to do the coordinate transformation. Is this right? I’ve done some roque testing with the component with no success. Could you help me?

  2. Hi Ville,

    Thanks for the feedback. I believe what you are describing is to “project” the data from one spatial reference to another. Take a look at https://developers.arcgis.com/javascript/latest/api-reference/esri-geometry-projection.html#project and see if that makes sense with your goals.

    We unfortunately don’t have the projection tool built into V1.0 of our library. I have opened an issue (https://github.com/dymaptic/GeoBlazor/issues/36) in our repository, and hope I can get you an updated version in the next few weeks. In the meantime, you could also write your own JavaScript interop like described in this blog post: https://www.dymaptic.com/creating-arcgis-web-mapping-apps-in-c-using-blazor/.

    I hope that helps! Please keep in touch, either here or feel free to email me.

    Sincerely,
    Tim Purdum
    Engineering Manager/Senior Engineer at dymaptic
    tim.purdum@dymaptic.com

  3. Thanks for your reply, Tim.
    Seems like projection might just be what i need.
    I’ll stay tuned for the updated version.

    br. Ville

  4. Ok, so the problem was indeed that i needed to translate or project ETRS-TM35FIN (EPSG:3067) coordinates to WGS84. I managed to do this using ProjNET4GeoAPI library and now everything is working as expected for me (at least for now :D). I’ll continue using this solution while you are working on ArcGIS js “native” solution wrapped to blazor.

    br. Ville

    1. Ville,

      Thanks for the update on your progress. While I am fully implementing the Projection tool, I also discovered that the ArcGIS maps will do a lot of “auto-projection”, as long as you define the incoming projection correctly, say by using a tag in your layer. I’ll also check out ProjNET4GeoAPI!

  5. Nice work. Like what I see, just one question, state management, let’s say you’ve got a map component and some other form centric component in blazor, you zoom and pan to a specific location on the map component, then switches to the form component to do some data capturing, if you go back to map component you’ve lost your zoom and location state, and thus everything are set to the default extent. How does one preserve the mapping extent between components

    1. Hi Ashley,

      If the two components you described (form and map) are on the same page, the state of the map will persist as you use the form. If you are talking about two pages, our next release (will definitely be this month!) will include MapView.GetExtent(), which will let you capture the current extent before moving on to another page. In general, I am currently working on a lot of additional features to provide this “feedback” loop to know the current state of components. If you have other specific questions or suggestions, please email me and let me know!

      Tim Purdum
      Engineering Manager/Senior Engineer at dymaptic
      tim.purdum@dymaptic.com

  6. I finally started implementing GeoBlazor to my own Blazor app (WASM & server shared component library).
    I have a slightly different navigation system compared to sample app so i separated the login procedures from MainLayout to a separate component that sits right at the App root in component tree, so it’s one of the first procedures that fire on app load. Im retrieving my ArcGIS ClientID and ApiKey from my web api endpoint. This whole procedure seems to be working fine. Although i cannot make any basic map render due to this error (copied from). Any suggestions how to fix this?

    Rendering View
    arcGisJsInterop.js:21 render map
    arcGisJsInterop.js:909 ReferenceError: require is not defined
    at Module.buildMapView (arcGisJsInterop.js:23:9)
    at blazor.webassembly.js:1:3332
    at new Promise ()
    at Object.beginInvokeJSFromDotNet (blazor.webassembly.js:1:3306)
    at Object.St [as invokeJSFromDotNet] (blazor.webassembly.js:1:59938)
    at _mono_wasm_invoke_js_blazor (dotnet.6.0.9.rnz6vdon31.js:1:195300)
    at 00971d1a:0x1a492
    at 00971d1a:0xce60
    at 00971d1a:0xbd73
    at 00971d1a:0xabebf
    blazor.webassembly.js:1

    crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
    Unhandled exception rendering component: Cannot read properties of null (reading ‘invokeMethodAsync’)
    TypeError: Cannot read properties of null (reading ‘invokeMethodAsync’)
    at logError (https://localhost:44380/_content/dymaptic.Blazor.GIS.API.Core/arcGisJsInterop.js:910:19)
    at Module.buildMapView (https://localhost:44380/_content/dymaptic.Blazor.GIS.API.Core/arcGisJsInterop.js:212:9)
    at https://localhost:44380/_framework/blazor.webassembly.js:1:3332
    at new Promise ()
    at Object.beginInvokeJSFromDotNet (https://localhost:44380/_framework/blazor.webassembly.js:1:3306)
    at Object.St [as invokeJSFromDotNet] (https://localhost:44380/_framework/blazor.webassembly.js:1:59938)
    at _mono_wasm_invoke_js_blazor (https://localhost:44380/_framework/dotnet.6.0.9.rnz6vdon31.js:1:195300)
    at wasm://wasm/00971d1a:wasm-function[219]:0x1a492
    at wasm://wasm/00971d1a:wasm-function[167]:0xce60
    at wasm://wasm/00971d1a:wasm-function[166]:0xbd73
    Microsoft.JSInterop.JSException: Cannot read properties of null (reading ‘invokeMethodAsync’)
    TypeError: Cannot read properties of null (reading ‘invokeMethodAsync’)
    at logError (https://localhost:44380/_content/dymaptic.Blazor.GIS.API.Core/arcGisJsInterop.js:910:19)
    at Module.buildMapView (https://localhost:44380/_content/dymaptic.Blazor.GIS.API.Core/arcGisJsInterop.js:212:9)
    at https://localhost:44380/_framework/blazor.webassembly.js:1:3332
    at new Promise ()
    at Object.beginInvokeJSFromDotNet (https://localhost:44380/_framework/blazor.webassembly.js:1:3306)
    at Object.St [as invokeJSFromDotNet] (https://localhost:44380/_framework/blazor.webassembly.js:1:59938)
    at _mono_wasm_invoke_js_blazor (https://localhost:44380/_framework/dotnet.6.0.9.rnz6vdon31.js:1:195300)
    at wasm://wasm/00971d1a:wasm-function[219]:0x1a492
    at wasm://wasm/00971d1a:wasm-function[167]:0xce60
    at wasm://wasm/00971d1a:wasm-function[166]:0xbd73
    at Microsoft.JSInterop.JSRuntime.d__16`1[[Microsoft.JSInterop.Infrastructure.IJSVoidResult, Microsoft.JSInterop, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].MoveNext()
    at Microsoft.JSInterop.JSObjectReferenceExtensions.InvokeVoidAsync(IJSObjectReference jsObjectReference, String identifier, Object[] args)
    at dymaptic.Blazor.GIS.API.Core.Components.Views.MapView.b__106_0()
    at dymaptic.Blazor.GIS.API.Core.Components.Views.MapView.RenderView(Boolean forceRender)
    at dymaptic.Blazor.GIS.API.Core.Components.Views.MapView.OnAfterRenderAsync(Boolean firstRender)
    at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState

    1. Ville,

      “Require is not defined” means that some JS modules are not being loaded. Double-check that you have the “Links” section in the header of `Index.html`:

      1. WordPress erased my html tags I tried to post, but the links section is defined above, but in reference to “_Layout.cshtml” which is the blazor server root. In your case, you need those tags in “Index.html”, and remove the double “@@” symbols.

      2. Got it working now. Thanks.

        There is lots of code in the sample project (MainLayout) related to logging in with OAuth2 and i found that most of if is not needed at all when loggin in with ApiKey. Got bit distracted by this. Guessing that ApiKey needs to be saved to browser local storage with the predefined key “ArcGISApiKey” so that js functions can read it from there correctly?

        1. Ville,

          Glad that helped! I am sorry there’s not more documentation, but it is definitely coming!

          You are correct that you don’t need to do OAuth2 if you have an APIKey. However, there is no really secure way to store the api key in WASM, where all your files are downloaded to the customer machine. So I suggest that the end user should either have to enter their own API key, set up a call to your own backend system to retrieve one, or have users sign in with OAuth.

          Adding `builder.Configuration.AddInMemoryCollection()` in Program.cs allows you to add config values on the fly. Then, use `IConfiguration` to load the value (`config[“ArcGISApiKey”] = yourToken`), and our library will find it automatically when running. At that point, you CAN save it to local storage, but again, just be aware of the security around that. If you can see the API Key anywhere in the browser dev tools, then your customer can as well.

          Also, just to let you know, I just published a new nuget package. We’re renaming to “GeoBlazor”, and the package is at https://www.nuget.org/packages/dymaptic.GeoBlazor.Core. The repo has a few new samples, including some around Projection. And check out https://github.com/dymaptic/GeoBlazor/blob/develop/UsingTheAPI.md, which is very similar to this post, but has my latest changes.

          Finally, we’re starting a Discord server for more Q&A. I think it will be easier to add questions and include code snippets there. https://discord.gg/hcmbPzn4VW

  7. I’m trying to view a feature layer from secure source, so i would need to add authorization header that gets included to messages that are sent with all request to this url. Seems like it’s not possible at the moment?

    Cannot intercept these requests using a custom delegating handler in my httpclient factory since these requests do not go trough there but instead the ArcGIS JS framework. My feature layer source supports passing authorization headers in query parameters but these get stripped away by the ArcGIS JS framework http message handler. Any suggestions?

    1. Hi Ville,

      Can you give me more info on what type of secured source you have? Is this an ArcGIS Portal, Server, or Enterprise? I don’t have all those possibilities added to GeoBlazor yet, but I might be able to help you write the JavaScript required to connect.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top