<rss version="2.0">
  <channel>
    <title>Rick Strahl's FoxPro Weblog</title>
    <link>https://webconnectionblog.west-wind.com/</link>
    <image>
      <url>ImageUrl</url>
      <title>Rick Strahl's FoxPro Weblog</title>
      <link>https://webconnectionblog.west-wind.com/</link>
    </image>
    <description>Wind, waves, code and everything in between</description>
    <copyright>(c) West Wind Technologies 2006-2026</copyright>
    <pubDate>2026-05-25T17:50:48.3507593Z</pubDate>
    <lastBuildDate>2025-08-04T10:38:39Z</lastBuildDate>
    <generator>Rick Strahl's West Wind Weblog</generator>
    <item>
      <title>What is CORS and how to set it up in West Wind Web Connection</title>
      <description>&lt;p&gt;&lt;img src="https://webconnectionblog.west-wind.com/imageContent/2026/What-is-CORS-and-how-to-set-it-up-in-West-Wind-Web-Connection/CORSBanner.jpg" alt="CORS Banner"&gt;&lt;/p&gt;
&lt;p&gt;CORS stands for &lt;strong&gt;Cross Origin Resource Sharing&lt;/strong&gt; and it's a security feature that you need to be aware of if you're building any Http based REST services that are called from a Web browser and that are accessed across multiple domains. This applies if you have a front-end Web site that runs on one domain, and a back-end server that lives on another domain (or IP address). CORS typically kicks in when making script based requests from a browser for these cross domain calls.&lt;/p&gt;
&lt;p&gt;The protocol is an odd one, in that it's typically enforced only by Web Browsers, and not in play for most other Http clients like say &lt;code&gt;wwHttp&lt;/code&gt; or &lt;code&gt;XMLHttpRequest&lt;/code&gt; in FoxPro, or &lt;code&gt;HttpClient&lt;/code&gt; in .NET unless you explicitly mimic the &lt;code&gt;Origin&lt;/code&gt; and &lt;code&gt;Access-Allow-Request&lt;/code&gt; headers that the protocol uses.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;When a browser makes a cross-origin &lt;code&gt;fetch()&lt;/code&gt; or &lt;code&gt;XMLHttpRequest&lt;/code&gt; call from script, it sends a request for CORS headers which the server should respond to with it's own set of response headers. The browser then checks the server’s response for specific CORS headers like &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt; and &lt;code&gt;Access-Control-Allow-Headers&lt;/code&gt;. If the headers match the requesting origin and other headers, the browser allows the calling JavaScript to access the response. If it doesn't match the browser doesn't expose the response to the calling JavaScript Http client and throws a script error on the request.&lt;/p&gt;
&lt;p&gt;The purpose of CORS is to allow the server to tell the Web Browser whether it is allowed access to the requested Url. Although CORS has to be implemented by the server, CORS is really &lt;strong&gt;a Web Browser security feature&lt;/strong&gt; that only is enforced by Web Browsers, but not other Http clients.&lt;/p&gt;
&lt;p&gt;For example the server may want to ensure that requests only come from one or two domains (origins) that are allowed to access the service when called from a browser. Note that CORS doesn't verify or authorize requests - it's merely a protocol feature that sends requesting headers that are confirmed by the server for a follow-on or in-flight request to process.&lt;/p&gt;
&lt;h3 id="web-browser-only-protocol-but-implemented-by-the-server"&gt;Web Browser Only Protocol but implemented by the Server&lt;/h3&gt;
&lt;p&gt;It's an odd protocol because &lt;strong&gt;it's only used in Web Browsers&lt;/strong&gt;, and mostly irrelevant for other Http clients. The reason is that the actual CORS 'security' feature - rejecting a request on invalid or missing CORS data - is implemented by the Web Browser Http client. So it's up to the client to provide this logic and in general only Web Browsers implement CORS security. Other Http never send CORS request nor do they process them or restrict access based on them.&lt;/p&gt;
&lt;h3 id="cors-doesnt-fire-on-localhost"&gt;CORS doesn't fire on localhost&lt;/h3&gt;
&lt;p&gt;It's also easy to forget about CORS during development, because CORS doesn't kick in when running &lt;strong&gt;same origin&lt;/strong&gt; requests. If your Web page and REST Service run on the same domain/IP Address, there's no CORS. During development that's very common, while deployed applications often run on separate servers. Also if you're using Http testing tools like &lt;a href="Https://websurge.west-wind.com"&gt;West Wind WebSurge&lt;/a&gt; or &lt;a href="Https://www.postman.com/"&gt;Postman&lt;/a&gt;, CORS doesn't automatically kick in unless you explicitly provide the CORS request headers like &lt;code&gt;Origin&lt;/code&gt; and any &lt;code&gt;Access-Allow-Request-xxxx&lt;/code&gt; headers. Hence it's easy to forget to test CORS use cases while testing REST applications. Make a point of remembering and/or ensuring you set up specific tests in your Http client request testing suite (you are using that with your services, right? 😄)&lt;/p&gt;
&lt;h2 id="the-cors-protocol"&gt;The CORS Protocol&lt;/h2&gt;
&lt;p&gt;CORS is implemented at the Http protocol level via Http headers that are passed from client to server, and back to the client.&lt;/p&gt;
&lt;p&gt;It works like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The client sends an &lt;code&gt;Origin&lt;/code&gt; header and potentially several &lt;code&gt;Access-Allow-Request-xxxx&lt;/code&gt; headers&lt;/li&gt;
&lt;li&gt;This signals that the server should return a CORS response&lt;/li&gt;
&lt;li&gt;The CORS response needs to include at minimum:
&lt;ul&gt;
&lt;li&gt;The domain that is allowed access (ie. the current domain)&lt;/li&gt;
&lt;li&gt;The Http Verbs that are allowed&lt;/li&gt;
&lt;li&gt;The Http Headers that are allowed&lt;/li&gt;
&lt;li&gt;Whether security (Authorization, Cookies) is allowed&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Depending what type of request you're making CORS data is either made via a separate, pre-flight Http &lt;code&gt;OPTIONS&lt;/code&gt; request, or via in-flight headers that are part of a 'normal' request flow.&lt;/p&gt;
&lt;h3 id="pre-flight-options-request"&gt;Pre-flight OPTIONS Request&lt;/h3&gt;
&lt;p&gt;For  most &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;, &lt;code&gt;PATCH&lt;/code&gt; operations browsers send a pre-flight &lt;code&gt;OPTIONS&lt;/code&gt; request to the server which requests a CORS header response. In effect this results in &lt;strong&gt;two requests&lt;/strong&gt; being made to the server by the browser: An &lt;code&gt;OPTIONS&lt;/code&gt; request for CORS verification, followed by the original full Http request.&lt;/p&gt;
&lt;p&gt;The Http &lt;code&gt;OPTIONS&lt;/code&gt; request from client requests &lt;strong&gt;only the Http headers&lt;/strong&gt; for the request and the response returned should contain only the Http headers that a server is expected to return for &lt;strong&gt;this request&lt;/strong&gt;, which &lt;strong&gt;includes the CORS headers&lt;/strong&gt;. The response code should return headers, no data and have a result Status Code  &lt;code&gt;204 No Data&lt;/code&gt; although &lt;code&gt;200 OK&lt;/code&gt; with no data also works - the browser doesn't really care about the result code or content, only the headers.&lt;/p&gt;
&lt;p&gt;Here's what that looks like:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://webconnectionblog.west-wind.com/imageContent/2026/What-is-CORS-and-how-to-set-it-up-in-West-Wind-Web-Connection/PreflightOptionsRequestion.png" alt="Preflight Options Requestion"&gt;&lt;br&gt;
&lt;small&gt;&lt;strong&gt;Figure 1&lt;/strong&gt; - A pre-flight OPTIONS CORS request&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;Note that this is an &lt;code&gt;OPTIONS&lt;/code&gt; request that was originally triggered by a &lt;code&gt;POST&lt;/code&gt; operation that also requires Authentication in this case. Note that the client sets &lt;code&gt;Access-Control-Request&lt;/code&gt; headers to specify what operations it wants to perform which the server response needs to include by adding the corresponding &lt;code&gt;Access-Control-Allow&lt;/code&gt; headers. Remember these are typically sent by a Web browser making a cross-origin request via script code.&lt;/p&gt;
&lt;p&gt;The server has to respond with headers that include the requested origin, headers and methods. If the CORS headers aren't matched the actual request (ie. the &lt;code&gt;POST&lt;/code&gt; request in this case) is never fired and the &lt;code&gt;OPTIONS&lt;/code&gt; request fails the original POST operation that initiated this sequence at the client (ie. the &lt;code&gt;fetch()&lt;/code&gt; or &lt;code&gt;XMLHttpRequest&lt;/code&gt; call).&lt;/p&gt;
&lt;p&gt;To clarify the full request flow in this example is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Browser makes a &lt;code&gt;fetch()&lt;/code&gt; request with  &lt;code&gt;POST&lt;/code&gt; and Authentication&lt;/li&gt;
&lt;li&gt;Browser fires &lt;code&gt;OPTIONS&lt;/code&gt; request&lt;/li&gt;
&lt;li&gt;Server responds with valid CORS response&lt;/li&gt;
&lt;li&gt;Browser makes the original &lt;code&gt;POST&lt;/code&gt; request&lt;/li&gt;
&lt;li&gt;Server responds to &lt;code&gt;POST&lt;/code&gt; request&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="in-flight-request"&gt;In-flight Request&lt;/h3&gt;
&lt;p&gt;For simple &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;POST&lt;/code&gt; requests that don't include authentication, cookie or custom headers, an &lt;code&gt;Origin&lt;/code&gt; header is sent without a separate explicit &lt;code&gt;OPTIONS&lt;/code&gt; request and only the single original request is sent. Instead the CORS headers are checked as part of the incoming request and your full response needs to include the CORS headers directly.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://webconnectionblog.west-wind.com/imageContent/2026/What-is-CORS-and-how-to-set-it-up-in-West-Wind-Web-Connection/InflightPostRequest.png" alt="Inflight Post Request"&gt;&lt;br&gt;
&lt;small&gt;&lt;strong&gt;Figure 2&lt;/strong&gt; - An in-flight CORS request adds CORS headers to the normal request and response headers. &lt;code&gt;Origin&lt;/code&gt; is the trigger.&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;Since in-flight requests don't use a separate request to determine whether the request can execute, it only includes an &lt;code&gt;Origin&lt;/code&gt; header to validate that the domain is allowed. Everything else is moot, because the request is already in process. POST operations sometimes send the &lt;code&gt;Access-Control-Request-Headers&lt;/code&gt;, but it's not required which means you can't just assume to echo back the Headers that were sent - or not add the CORS headers if you decide to not reject the CORS request.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3 id="cors-failure-and-success-responses"&gt;CORS Failure and Success Responses&lt;/h3&gt;
&lt;p&gt;If the client makes a CORS request and your server code decides it doesn't want to allow the request to process, there are number of ways to do this.&lt;/p&gt;
&lt;p&gt;For failures simply return no content with &lt;code&gt;403 Forbidden&lt;/code&gt;, and don't add any of the CORS headers, which effectively fails the CORS request on the client. Any &lt;code&gt;fetch()&lt;/code&gt; or &lt;code&gt;XMLHttpRequest&lt;/code&gt; call will then fail in JavaScript code.&lt;/p&gt;
&lt;p&gt;For a successful &lt;code&gt;OPTIONS&lt;/code&gt; response use &lt;code&gt;204 No Content&lt;/code&gt;, make sure to return the CORS response headers and then exit &lt;strong&gt;before processing the rest of the request&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For in-flight non-OPTIONS responses, make sure to add the CORS response headers, and continue processing your request as normal. No need to adjust any special Http Response code.&lt;/p&gt;
&lt;blockquote&gt;
&lt;h5 id="--beware-of-access-control-allow-origin-"&gt;&lt;i class="fas fa-warning" style="font-size: 1.1em"&gt;&lt;/i&gt;  Beware of Access-Control-Allow-Origin: *&lt;/h5&gt;
&lt;p&gt;In previous versions of Web Connection (and other server frameworks for that matter) the default generated CORS response for 'all origins allowed'  was to specify &lt;code&gt;*&lt;/code&gt; for the origin value. Although this is a valid value per spec, in recent years several browsers - namely Safari on Mac and iOS - are refusing to accept any * wildcard values for any of the CORS response headers.&lt;/p&gt;
&lt;p&gt;For this reason as of Web Connection 8.5 the templates have changed from the old &lt;code&gt;Access-Control-Allow-Origin: *&lt;/code&gt; syntax to explicitly retrieving the &lt;code&gt;Origin&lt;/code&gt; header and echoing it back in the outgoing header value. The code at the end of this article shows the new approach that works in this more restricted environment.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="cors-in-web-connection"&gt;CORS in Web Connection&lt;/h2&gt;
&lt;p&gt;Web Connection doesn't have direct support for CORS as part of the low level Web Connection Web Server connectors, so CORS support has to be implemented at the application level. It's an optional feature and typically only needed for REST projects, although in  some rare situations you may also need it if you have individual REST requests as part of an HTML application (rare, but it happens).&lt;/p&gt;
&lt;p&gt;When you create a new REST project via the &lt;strong&gt;New Project Wizard&lt;/strong&gt; Web Connection automatically adds CORS support into the generated Process class in &lt;code&gt;OnProcessInit()&lt;/code&gt;. The code checks for an &lt;code&gt;Origin&lt;/code&gt; header and then displays the appropriate CORS response headers, based on the incoming request. This ensures that valid cross-origin requests are properly acknowledged and allowed by the browser.&lt;/p&gt;
&lt;p&gt;The semi generic code to do this lives in your Process class in &lt;code&gt;OnProcessInit()&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-foxpro"&gt;FUNCTION OnProcessInit
LOCAL lcOrigin, lcRequestHeaders, lcVerb

*** Unrelated
Response.Encoding = &amp;quot;UTF8&amp;quot;
Request.lUtf8Encoding = .T.

*** Add CORS headers to allow cross-site access on REST calls for browser access
lcOrigin = Request.ServerVariables(&amp;quot;Http_ORIGIN&amp;quot;)

IF !EMPTY(lcOrigin) 
	*** Allow all domains IP addresses effectively
	Response.AppendHeader(&amp;quot;Access-Control-Allow-Origin&amp;quot;, lcOrigin)  
	Response.AppendHeader(&amp;quot;Access-Control-Allow-Methods&amp;quot;,&amp;quot;POST, GET, DELETE, PUT, OPTIONS&amp;quot;)	
	
	lcRequestHeaders = Request.GetExtraHeader(&amp;quot;Access-Control-Request-Headers&amp;quot;)
	if EMPTY(lcRequestHeaders)
	  lcRequestHeaders = &amp;quot;Content-Type, Authorization&amp;quot; &amp;amp;&amp;amp; ,Cookie if you use cookie auth!
	endif
	Response.AppendHeader(&amp;quot;Access-Control-Allow-Headers&amp;quot;, lcRequestHeaders)
	Response.AppendHeader(&amp;quot;Access-Control-Allow-Credentials&amp;quot;,&amp;quot;true&amp;quot;)
ENDIF

lcVerb = Request.GetHttpVerb()
IF (lcVerb == &amp;quot;OPTIONS&amp;quot;)
    Response.Status = &amp;quot;204 No Content&amp;quot;
	RETURN .F.  &amp;amp;&amp;amp; Stop Processing
ENDIF

*** ... other OnProcessInit() code
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code first checks for the &lt;code&gt;Origin&lt;/code&gt; client header, which determines whether the request is cross-site and requires the server to return a CORS response. No &lt;code&gt;Origin&lt;/code&gt; means no CORS data is required on the request. So local domain access, or access from a non-Web Browser request won't trigger CORS requests.&lt;/p&gt;
&lt;p&gt;When a CORS request comes in here are the items required:&lt;/p&gt;
&lt;h3 id="original-origins"&gt;Original Origins&lt;/h3&gt;
&lt;p&gt;Next this code implements the default &lt;code&gt;Origin&lt;/code&gt; behavior which returns the origin requested, which &lt;strong&gt;effectively allows access to all domains&lt;/strong&gt; since we're always returning what the client requests.&lt;/p&gt;
&lt;p&gt;If you want to allow only certain domains, you can create some sort of lookup function or even a hard code a list of domains to allow. If a CORS request fails you can return a &lt;code&gt;403 Forbidden&lt;/code&gt; status - and not return the CORS headers.&lt;/p&gt;
&lt;p&gt;Here's what that looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-foxpro"&gt;IF !EMPTY(lcOrigin) 
    IF !THIS.CheckForValidOrigin(lcOrigin)
       Response.Status = &amp;quot;403 Forbidden&amp;quot;
       RETURN .F.
    ENDIF 
    
	Response.AppendHeader(&amp;quot;Access-Control-Allow-Origin&amp;quot;, lcOrigin)  
    ...
ENDIF
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="methods"&gt;Methods&lt;/h3&gt;
&lt;p&gt;You need to specify the allowed methods when OPTIONS requests are made. In OPTIONS requests the client will send &lt;code&gt;Access-Control-Request-Methods&lt;/code&gt; which you can echo back. Not though that this value is not present in inflight requests so it's better to use a fixed set of Http Verbs that you are planning to use in your project.&lt;/p&gt;
&lt;p&gt;Easiest just to return all the methods your app supports, but you could also return the specific verb being requested.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-foxpro"&gt;Response.AppendHeader(&amp;quot;Access-Control-Allow-Methods&amp;quot;,&amp;quot;POST, GET, DELETE, PUT, OPTIONS&amp;quot;)	
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that in an &lt;code&gt;OPTIONS&lt;/code&gt; command you need to include &lt;code&gt;OPTIONS&lt;/code&gt; plus &lt;code&gt;Access-Control-Request-Methods&lt;/code&gt; rather than just using &lt;code&gt;Request.GetHttpVerb()&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="headers"&gt;Headers&lt;/h3&gt;
&lt;p&gt;This one is a little tricky since you may not know what headers you need to support for all requests. &lt;code&gt;OPTIONS&lt;/code&gt; requests provide an explicit &lt;code&gt;Access-Control_Request-Header&lt;/code&gt; value that you can echo back, but again there's no guarantee that this value exists so you want to make sure you have a default value ready using this logic:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-foxpro"&gt;lcRequestHeaders = Request.GetExtraHeader(&amp;quot;Access-Control-Request-Headers&amp;quot;)
IF EMPTY(lcRequestHeaders)
  lcRequestHeaders = &amp;quot;Content-Type, Authorization&amp;quot; &amp;amp;&amp;amp; ,Cookie if you use cookie auth or Sessions!
ENDIF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The tricky bit here is that &lt;strong&gt;you need to provide all custom headers&lt;/strong&gt; that you might send. While that may seem easy for individual requests, it's more difficult to figure out exactly what's needed for &lt;em&gt;every request&lt;/em&gt; in an application and distill that down to a single set of headers. Hence you'll want to use the requested headers if available. If you have custom headers it'll always trigger an &lt;code&gt;OPTIONS&lt;/code&gt; request, so in that case the &lt;code&gt;Allow-Control-Request-Headers&lt;/code&gt; should be sent with the incoming request and that's what you should return in your CORS header response.&lt;/p&gt;
&lt;h3 id="allow-credentials"&gt;Allow Credentials&lt;/h3&gt;
&lt;p&gt;If your app has any authentication you'll want to set this value to true. This is needed if you have &lt;code&gt;Authorization&lt;/code&gt; or &lt;code&gt;Cookie&lt;/code&gt; headers.&lt;/p&gt;
&lt;p&gt;Kind of silly that this is required since the headers should be able to determine whether this is required.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;CORS is a clunky protocol and when you first look at it, it doesn't seem to be very effective at providing any security at all. However, for browsers it is useful in ensuring that errand Web browsers can't spoof requests from an embedded iframe for example. The server can explicitly check valid source domains and refuse to serve requests if the list of client domains is not met. However, that does not negate the problem because non-Web Browser clients can call your server any way they want - including potentially spoofed origin domains.&lt;/p&gt;
&lt;p&gt;The good news is that it's easy to implement CORS for your REST services in Web Connection. There's only a little bit of code required, and in can be placed into a single entry point in &lt;code&gt;OnProcessInit()&lt;/code&gt; in one place. If you're creating new REST projects, Web Connection automatically provides the CORS code in the generated process class and if you have existing code you can easily copy in the code from this article...&lt;/p&gt;
&lt;div style="margin-top: 30px;font-size: 0.8em;
            border-top: 1px solid #eee;padding-top: 8px;"&gt;
    &lt;img src="Https://markdownmonster.west-wind.com/favicon.png" style="height: 20px;float: left; margin-right: 10px;"&gt;
    this post created and published with the 
    &lt;a href="Https://markdownmonster.west-wind.com" target="top"&gt;Markdown Monster Editor&lt;/a&gt; 
&lt;/div&gt;
</description>
      <link>https://webconnectionblog.west-wind.com/posts/2025/Aug/04/What-is-CORS-and-how-to-set-it-up-in-West-Wind-Web-Connection</link>
      <guid isPermaLink="false">57038</guid>
      <author> (Rick Strahl)</author>
      <comments>https://webconnectionblog.west-wind.com/posts/2025/Aug/04/What-is-CORS-and-how-to-set-it-up-in-West-Wind-Web-Connection#Comments</comments>
      <guid>https://webconnectionblog.west-wind.com/posts/2025/Aug/04/What-is-CORS-and-how-to-set-it-up-in-West-Wind-Web-Connection</guid>
      <pubDate>Mon, 04 Aug 2025 00:38:39 GMT</pubDate>
    </item>
    <item>
      <title>Web Connection 8.4 Release Post</title>
      <description>&lt;p&gt;&lt;img src="https://webconnection.west-wind.com/images/WebConnection_Code_Banner.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Hi all,&lt;/p&gt;
&lt;p&gt;I've released Web Connection 8.4 which is a minor maintenance release of the FoxPro Web and Service Development framework. The primary reason for this release are several small bug fixes that might be impacting some of you if you are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using wwUserSecurity Authentication (especially in Virtual Folders)&lt;/li&gt;
&lt;li&gt;You're using Multipart Form Uploads via wwHttp&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If that's you and you're using v8.1-8.3, you'll probably want to update to the latest version as soon as possible.&lt;/p&gt;
&lt;p&gt;There are also a couple of nice enhancements in wwDotnetBridge, and a new documentation viewer offline application.&lt;/p&gt;
&lt;h2 id="bug-fixes"&gt;Bug Fixes&lt;/h2&gt;
&lt;p&gt;Let's start with the important bug fixes because they are the main reason for this release.&lt;/p&gt;
&lt;h3 id="fix-user-authentication-session-cookie-bug"&gt;Fix User Authentication Session Cookie Bug&lt;/h3&gt;
&lt;p&gt;In the last 8.2-8.3 a regression error was introduced that in certain situations would not properly set the wwUserSecurity related authentication Session cookie. Specifically this can be a problem in scenarios where you're using a non-root virtual folder in your Web application.&lt;/p&gt;
&lt;p&gt;I'm not quite sure why this particular error occurred only with virtual folders since Web Connection cookies are always set on the domain root, but for some reason cookies set when running under the virtual in some cases would not be set properly. The issue basically was that a session Id was present but for some reason the session was not recovered and so a new session key gets generated on each check attempt which effectively results in a successful login that lasts only for the current requests. Oddly this only occurs in virtuals and then more likely on nested virtuals (for me it happened in the &lt;a href="https://west-wind.com/wconnect/weblog"&gt;Web Connection Weblog&lt;/a&gt; specifically).&lt;/p&gt;
&lt;p&gt;It's fixed now where the code now explicitly generates a new cookie on login rather than checking for an existing cookie to update.&lt;/p&gt;
&lt;h3 id="fix-wwhttp-multipart-form-data-content-type-not-getting-set"&gt;Fix: wwHttp Multipart Form Data Content Type not getting set&lt;/h3&gt;
&lt;p&gt;This issue is another regression that cropped up from the recent work to support greater than 16mb uploads and downloads. Essentially, the Content Type parameter on &lt;a href="https://webconnection.west-wind.com/docs/Utility-Classes/West-Wind-Internet-Protocols/Class-wwHTTP/wwHTTPAddPostKey.html"&gt;AddPostKey()&lt;/a&gt; was not being passed through in to the request. It does work for the newly added &lt;code&gt;AddPostFile()&lt;/code&gt; but if you're using older code that uses &lt;code&gt;AddPostKey()&lt;/code&gt; to upload files the content type was not being appended.&lt;/p&gt;
&lt;p&gt;In the process of fixing this bug, I also cleaned up the signature to the &lt;a href="https://webconnection.west-wind.com/docs/Utility-Classes/West-Wind-Internet-Protocols/Class-wwHTTP/wwHTTPAddPostFile.html"&gt;AddPostFile()&lt;/a&gt; method. The method initially had inherited the same signature as &lt;code&gt;AddPostKey()&lt;/code&gt; but there were several parameters that are superfluous for uploading files, so the signature was adjusted for more logical flow.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The change to &lt;code&gt;AddPostKey&lt;/code&gt; is a potential breaking change if you were using the method since the order of parameters has changed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="missing-jsonservice-variable-in-wwrestprocess-process-methods"&gt;Missing JsonService Variable in wwRestProcess Process Methods&lt;/h3&gt;
&lt;p&gt;Another regression bug relates to &lt;a href="https://webconnection.west-wind.com/docs/Framework-Classes/Class-wwRestProcess.html"&gt;wwRestService&lt;/a&gt; and the use of the intrinsic &lt;code&gt;JsonService&lt;/code&gt; variable that is passed as a shortcut for &lt;code&gt;THIS.oJsonService&lt;/code&gt; into a REST Process method. The error was due a missing &lt;code&gt;PRIVATE&lt;/code&gt; scope assignment at the top of the processing pipeline in &lt;code&gt;RouteRequest()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This has been fixed.&lt;/p&gt;
&lt;h2 id="new-and-improved-features"&gt;New and Improved Features&lt;/h2&gt;
&lt;p&gt;Not much to report in this update but there are few.&lt;/p&gt;
&lt;h3 id="wwdotnetbridge-unblock-all-dlls-loaded"&gt;wwDotnetBridge: Unblock all DLLs loaded&lt;/h3&gt;
&lt;p&gt;The classic .NET Framework still uses Windows Vista Style zoning security by default and so respects the custom blocking attributes that are added to binary files downloaded from the Internet (including files embedded in Zip or 7zip files when unpacked), media devices or from non-local domain drives. Files from those sources get implicitly marked by Windows as 'blocked'.&lt;/p&gt;
&lt;p&gt;wwDotnetBridge has for some time explicitly removed blocks on &lt;code&gt;wwDotnetBridge.dll&lt;/code&gt; which is the initially loaded Dll that loads the runtime. In most cases that's enough, but I've recently run into cases where other DLLs being loaded were also affected and failed when calling &lt;code&gt;loBridge.LoadAssembly()&lt;/code&gt;.  For this reason, wwDotnetBridge now also removes blocks from any assembly loaded via &lt;code&gt;LoadAssembly()&lt;/code&gt; before loading.&lt;/p&gt;
&lt;p&gt;This should improve issues with blocked DLL loading significantly, but you may &lt;strong&gt;still run into problems&lt;/strong&gt; if the DLLs loaded load other DLLs implicitly. In that case you can explicitly load those assemblies using &lt;code&gt;LoadAssembly()&lt;/code&gt; (starting with the most deeply nested dependency first), or you can manually unblock files as described in this &lt;a href="https://webconnection.west-wind.com/docs/Utility-Classes/Class-wwDotnetBridge/Unable-to-load-CLR-Instance-Error.html"&gt;help topic.&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="new-offline-documentation-viewer"&gt;New Offline Documentation Viewer&lt;/h3&gt;
&lt;p&gt;For years several people here - looking at you Tore - have hounded me to provide offline documentation again as we used some time ago. In the past I created a very large PDF document which was problematic to create as I used Word Automation to import from Html and then Print to Pdf. That process was very brittle and took a really long time to run and often would randomly fail.&lt;/p&gt;
&lt;p&gt;Well, recently I switched the documentation over to a new documentation system I've been building called &lt;a href="https://documentationmonster.com"&gt;Documentation Monster&lt;/a&gt;, which is a Help Builder like system built ontop of my popular &lt;a href="https://markdownmonster.west-wind.com"&gt;Markdown Monster&lt;/a&gt; editor.&lt;/p&gt;
&lt;p&gt;As part of that tool there's a new option to create a self contained documentation viewer application that can be used to browse the online documentation offline.&lt;/p&gt;
&lt;p&gt;Here's what that looks like for Web Connection:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://webconnection.west-wind.com/docs/images/WebConnectionDocumentationViewer.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;All the functionality of the Web site is available in the offline Viewer so you get all the same docs - offline.&lt;/p&gt;
&lt;p&gt;The big benefit from my end is that's easy and fast to build so this documentation is much easier to keep in sync with the online documentation.&lt;/p&gt;
&lt;p&gt;The tool produces a self contained EXE that itself is very small. The size of the EXE is determined primarily by the size of the documentation which is embedded inside of the Exe and unpacked when run. That said the Web Connection file is still in the 20mb range zipped due to the huge amount of content and media. The Exe has no dependencies as it runs on the built-in .NET framework.&lt;/p&gt;
&lt;p&gt;You can check it out from here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://webconnection.west-wind.com/docs/West-Wind-Web-Connection/Offline-Documentation.html"&gt;Download the Web Connection Documentation Offline Viewer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;Overall this is a very small release that's primarily been released for the bug fixes (which have been avaiable for a while in the Experimental Updates Zip file as soon as they were discovered and fixed).&lt;/p&gt;
&lt;p&gt;I would recommend updating to the latest version if you are on 8.1-8.3.&lt;/p&gt;
</description>
      <link>https://webconnectionblog.west-wind.com/posts/2025/Jul/23/Web-Connection-84-Release-Post</link>
      <guid isPermaLink="false">57037</guid>
      <author> (Rick Strahl)</author>
      <comments>https://webconnectionblog.west-wind.com/posts/2025/Jul/23/Web-Connection-84-Release-Post#Comments</comments>
      <guid>https://webconnectionblog.west-wind.com/posts/2025/Jul/23/Web-Connection-84-Release-Post</guid>
      <pubDate>Wed, 23 Jul 2025 01:32:21 GMT</pubDate>
    </item>
    <item>
      <title>Creating and Debugging .NET Assemblies for wwDotnetBridge and Visual FoxPro</title>
      <description>&lt;p&gt;&lt;img src="https://webconnectionblog.west-wind.com/imageContent/2025/CreatingAndDebuggingDotnetAssembliesForwwDotnetBridge/HackingBanner.jpg" alt="Hacking Banner"&gt;&lt;/p&gt;
&lt;p&gt;If you're using &lt;a href="https://github.com/RickStrahl/wwDotnetBridge"&gt;wwDotnetBridge&lt;/a&gt; with FoxPro it's quite likely that at some point you'll want to create your own .NET Dlls that interface with external .NET code. While wwDotnetBridge allows you to interface with .NET directly, the code you have to write can be quite tedious because for many things you have explicitly reference the intrinsic methods like &lt;code&gt;InvokeMethod()&lt;/code&gt;, &lt;code&gt;SetProperty()&lt;/code&gt;, &lt;code&gt;GetProperty()&lt;/code&gt; to interact with data that is not compatible over straight COM. While most of these things can be done from FoxPro, the process of doing so is often verbose and in some cases requires multiple objects to be passed around. These intrinsic type scenarios are also relatively slow - compared to native code that is directly executed from .NET. At minimum every .NET call involves a COM call between FoxPro and .NET and when using the Intrinsic methods using Reflection in .NET. It's not horribly slow but compared to native .NET code directly called from .NET it's definitely slower.&lt;/p&gt;
&lt;p&gt;Additionally using .NET natively gives you much easier discovery of what's available via the .NET IDE tools that provide rich Intellisense, code completion, CoPilot, Refactoring and easy code navigation. All of this adds up to a richer experience.&lt;/p&gt;
&lt;p&gt;Now if you're making only one or two calls into a .NET library from FoxPro it's probably not worth creating a separate .NET component. But if you're making a lot of calls back and forth or the code is performance sensitive, then it may very well be worth your time to create a wrapper .NET compoent that you make one or two consolidated calls to and which then handles many .NET operations or entire component functionality.&lt;/p&gt;
&lt;p&gt;I am a big fan of building wrapper components in .NET where you can pass a few key values of input to the wrapper from FoxPro using COM optimized types, and the wrapper then does the brunt of work using native .NET code. If you look at most West Wind components like &lt;code&gt;wwSmtp&lt;/code&gt;, &lt;code&gt;wwFtpClient&lt;/code&gt;, &lt;code&gt;MarkdownParser&lt;/code&gt;, this is exactly how those components are built - there's a .NET class that exposes most of the functionality with a few very focused method calls that combine hundreds of .NET calls and a FoxPro front end abstraction that calls into the wrapper. This is by far my preferred way of exposing functionality from .NET libraries to FoxPro as it optimizes each platform for what it does best.&lt;/p&gt;
&lt;p&gt;I know many of you old FoxPro dogs want to do everything in FoxPro, but one of the big advantages of wwDotnetBridge is that it lets you bridge the gap between FoxPro and .NET. It provides an easy on-ramp for experimenting and integrating .NET code into your own applications one piece of code at a time. Whether you implement a single .NET method that you call from FoxPro, a full class, or a whole complex API of components or business objects - you can choose your own pace. Because you're calling a library you can choose how modular you want to go. It's a practical way to create small islands of functionality outside of FoxPro that still can easily integrate with your FoxPro application transparently.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2 id="creating-your-own-net-components"&gt;Creating your own .NET Components&lt;/h2&gt;
&lt;p&gt;So when you build a .NET component to interface with, you're going to create a &lt;strong&gt;Library Project&lt;/strong&gt; in .NET. A .NET Class library is essentially library of potentially unrelated classes that doesn't have a 'startup' class. A library is a project without an entry point: It's not a Desktop or Web or Console project, but it's just one or more .NET types (classes, structs, enums etc.) that can be called directly from .NET or... from FoxPro using wwDotnetBridge.&lt;/p&gt;
&lt;p&gt;Any .NET class library that you create can be instantiated and called via wwDotnetBridge. IOW - you create a class in .NET, put it somewhere FoxPro can find it and call it with wwDotnetBridge:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-foxpro"&gt;do wwDotnetBridge  &amp;amp;&amp;amp; load lib
loBridge = GetwwDotnetBridge()

llResult = loBridge.LoadAssembly(&amp;quot;bin\MyFirstLibrary.dll&amp;quot;)
? loBridge.cErrorMsg  &amp;amp;&amp;amp; if there's an error and llResult is false

loPerson = loBridge.CreateInstance(&amp;quot;MyFirstLibrary.Person&amp;quot;)
loPerson.Firstname = &amp;quot;Rick&amp;quot;
loPerson.Lastname = &amp;quot;Strahl&amp;quot;
loPerson.Address = loBridge.CreateInstance(&amp;quot;MyFirstLibrary.Address&amp;quot;)
loPerson.Address.Street = &amp;quot;999 Emergency&amp;quot;
loPerson.Address.PostalCode = &amp;quot;97000&amp;quot;
loPerson.Entered = DATETIME()
loPerson.Save()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To create a minimal .NET project literally takes two files: A project file and a source code file that contains one or more classes  that can be called. If you need more classes or components you can add them to the same file or create new classes. The new .NET project system automatically picks up and compiles all source files it finds so it's easy to add functionality.&lt;/p&gt;
&lt;p&gt;There are a number of ways to create a new project, but due to the fact that recent versions of Visual Studio and the &lt;code&gt;dotnet new&lt;/code&gt; CLI don't include templates for new .NET Framework (net4.x) projects.&lt;/p&gt;
&lt;p&gt;While you can use the tooling, but my preferred way of doing this is just creating the project file and the main class by hand (or &lt;a href="https://github.com/RickStrahl/swfox2024-wwdotnetbridge-revisited/tree/master/Dotnet/New%20Project%20net472%20Template"&gt;copying an existing project template from here&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;There are two reasons for this :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;.NET Tools (Visual Studio and the dotnet CLI) don't support creating new .NET 4.x projects&lt;/li&gt;
&lt;li&gt;It's easy to copy a couple of files to disk&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Although neither Visual Studio, the &lt;code&gt;dotnet&lt;/code&gt; command line or VS Code support creating .NET Framework projects, you can definitely build them once created. They are just hiding .NET Framework projects because Microsoft is trying to push everyone to .NET Core.&lt;/p&gt;
&lt;p&gt;However, I &lt;strong&gt;highly recommend&lt;/strong&gt; that you create .NET Framework (.NET 4.x) projects rather than .NET Core projects for your components unless you have a pressing need for .NET Core features or you need to interface with another library that does not support .NET 4.x or .NET Standard 2.0 which both can be used with the built-in .NET Framework.&lt;/p&gt;
&lt;p&gt;The reason is simply that the .NET Framework is built-into Windows so there's nothing to install. You can create a .NET Framework project compile and run without any other requirements other than the tiny DLL created (plus any dependent assemblies your component references). .NET Core on the other hand requires that a compatible runtime is installed, which means you have to make sure that your application checks and deploys the right runtime. There's no such requirement for .NET Framework.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you build for .NET Framework chances are that you can also move the code to .NET Core easily by adding a second target, so this is not a 1-way street or you can multi-target and actually support both. Difference between them are slight especially if you stick to core functionality.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3 id="creating-a-new-project-by-hand"&gt;Creating a new Project 'by hand'&lt;/h3&gt;
&lt;p&gt;There are a number of ways of creating a .NET project, but here's what I do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a new folder&lt;/li&gt;
&lt;li&gt;Open Visual Studio Code in that folder initially (or any other editor)&lt;/li&gt;
&lt;li&gt;Create the following two Files in there (Project and first component)&lt;/li&gt;
&lt;li&gt;Use the command line tools to build and run to test the component&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I do this even if I have Visual Studio installed. Why? As mentioned neither Visual Studio or the &lt;code&gt;dotnet new&lt;/code&gt; CLI has support for .NET framework. While you can create a new project for &lt;code&gt;netstandard2.0&lt;/code&gt; which is &lt;em&gt;close&lt;/em&gt; to what you need - you need to make a few small changes to the project. So rather than screw around with this changing template and remove things that don't work,
I use either a template from disk or manually create the files and copy in the small bits of project and initial class code.&lt;/p&gt;
&lt;p&gt;Start by creating creating a new folder for your project and opening an editor there:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ps"&gt;# create and goto  directory
cd D:\wwapps\Conf\wwDotnetBridgeRevisited\Dotnet\FirstLibrary

# Open VS Code in folder mode
code .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create two files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FirstProject.csproj&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Person.cs&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here's the project:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-xml"&gt;&amp;lt;!-- FirstLibrary.csproj --&amp;gt;
&amp;lt;Project Sdk=&amp;quot;Microsoft.NET.Sdk&amp;quot;&amp;gt;

  &amp;lt;PropertyGroup&amp;gt;    
    &amp;lt;TargetFramework&amp;gt;net472&amp;lt;/TargetFramework&amp;gt;    

    &amp;lt;!-- Optional: Output to a specific folder. Relative or absolute --&amp;gt;    
    &amp;lt;OutputPath&amp;gt;..\..\bin&amp;lt;/OutputPath&amp;gt;
    &amp;lt;AppendTargetFrameworkToOutputPath&amp;gt;false&amp;lt;/AppendTargetFrameworkToOutputPath&amp;gt;
   
  &amp;lt;/PropertyGroup&amp;gt;
&amp;lt;/Project&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This sets the target .NET 4.72 or later and puts the output DLL into the specified folder. The folder is optional - if you don't specify it goes into a deep folder hierarchy below the project. Typically you don't want this.&lt;/p&gt;
&lt;p&gt;Personally I like to put all my .NET assemblies either into my application's root folder (if there's only one) or &lt;code&gt;.\BinSupport&lt;/code&gt; folder below it for many Dlls.&lt;/p&gt;
&lt;p&gt;Next you need to create at least a single &lt;code&gt;.cs&lt;/code&gt; source file with a class in it. I'll create the &lt;code&gt;Person&lt;/code&gt; and &lt;code&gt;Address&lt;/code&gt; classes I used in the previous example here:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;using System;
using System.Collections.Generic;

namespace FirstLibrary
{
    public class Person
    {
        public string Firstname {get; set; }
        
        public string Lastname {get; set; }
        
        public DateTime Entered {get; set; }  = DateTime.Now;

        public Address Address {get; set;} = new Address();
        
        public bool Save()
        {
            // do whatever to save here
            return true;
        }

        public override string ToString()
        {
            return Firstname + &amp;quot; &amp;quot; + Lastname ?? string.Empty;
        }
    }

    public class Address
    {
        public string Street {get; set; }
        public string City {get; set; }
        public string State {get; set; }
        public string Zip  {get; set; }

        public override string ToString()
        {
            return Street + &amp;quot;\r\n&amp;quot; + City + &amp;quot;\r\n&amp;quot; + State + &amp;quot; &amp;quot; + Zip;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next we need to build the project. You can open a Terminal (Powershell here) either in Visual Studio Code or directly on the command line in the project folder and then build the project.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ps"&gt;dotnet build FirstLibrary.csproj
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This builds the project into the output folder which happens to live below my FoxPro project folder as &lt;code&gt;,\bin&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I can then call the and the component from FoxPro with:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-foxpro"&gt;CLEAR
do wwDotnetBridge  &amp;amp;&amp;amp; load lib
loBridge = GetwwDotnetBridge()

llResult = loBridge.LoadAssembly(&amp;quot;.\bin\FirstLibrary.dll&amp;quot;)
? loBridge.cErrorMsg  &amp;amp;&amp;amp; if there's an error and llResult is false

loPerson = loBridge.CreateInstance(&amp;quot;FirstLibrary.Person&amp;quot;)
loPerson.Firstname = &amp;quot;Rick&amp;quot;
loPerson.Lastname = &amp;quot;Strahl&amp;quot;
loPerson.Address = loBridge.CreateInstance(&amp;quot;FirstLibrary.Address&amp;quot;)
loPerson.Address.Street = &amp;quot;999 Emergency&amp;quot;
loPerson.Address.City = &amp;quot;AnyTown&amp;quot;
loPerson.Address.PostalCode = &amp;quot;97000&amp;quot;
loPerson.Entered = DATETIME()
loPerson.Save()

? loPerson.ToString()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Altogether it looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://webconnectionblog.west-wind.com/imageContent/2025/CreatingAndDebuggingDotnetAssembliesForwwDotnetBridge/BuildingAndRunning.png" alt="Building And Running"&gt;&lt;/p&gt;
&lt;p&gt;The above does:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Builds the project and compiles into the app folder (....\bin)&lt;/li&gt;
&lt;li&gt;Open FoxPro&lt;/li&gt;
&lt;li&gt;Runs the test.prg file&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cool it works.&lt;/p&gt;
&lt;h3 id="shutting-down-visual-foxpro-to-unload-net-assembly"&gt;Shutting down Visual FoxPro to Unload .NET Assembly&lt;/h3&gt;
&lt;p&gt;.NET and any components cannot be unloaded once loaded, so in order to update the DLL you have to shut down the host process, which in this case is Visual FoxPro (or your app EXE).&lt;/p&gt;
&lt;p&gt;If you need to make any changes to the DLL code, you need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Shut down Visual FoxPro (or EXE)&lt;/li&gt;
&lt;li&gt;Rebuild the .NET project&lt;/li&gt;
&lt;li&gt;Restart Visual FoxPro&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To make this cycle a little easier I tend to create a small Powershell (or Command)  script that I can run from the terminal in the project folder:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ps"&gt;dotnet build .\FirstLibrary.csproj

if ($LASTEXITCODE -ne 0) {
    exit
}

$startdir = $pwd

Set-Location ..\..
&amp;amp; D:\programs\vfp9\vfp9.exe
Set-Location $startdir
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This script handles building the project and launching VFP. It doesn't shut down FoxPro first - you'll have to do that manually, but building and starting up FoxPro is handled to make the launch a bit quicker than manually fumbling around to launch VFP in the right place.&lt;/p&gt;
&lt;p&gt;All of this works fine and it's pretty low impact so far. No need to install anything beyond the .NET SDK and VS Code (or your editor of choice).&lt;/p&gt;
&lt;p&gt;But things could be a little more integrated. And that's where Visual Studio comes in.&lt;/p&gt;
&lt;p&gt;If you want:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A smoother build and run cycle&lt;/li&gt;
&lt;li&gt;Ability to debug your DLL&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;then using Visual Studio is a worthwhile upgrade to the dev process.&lt;/p&gt;
&lt;h3 id="opening-the-project-in-visual-studio"&gt;Opening the Project in Visual Studio&lt;/h3&gt;
&lt;p&gt;Although I used Visual Studio Code (or no editor/IDE at all) to create the project originally, I can now also open this project in Visual Studio by opening the &lt;code&gt;FirstProject.csproj&lt;/code&gt; from Explorer and selecting &lt;em&gt;Visual Studio&lt;/em&gt; to open:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://webconnectionblog.west-wind.com/imageContent/2025/CreatingAndDebuggingDotnetAssembliesForwwDotnetBridge/OpenProjectInVisualStudio.png" alt="Open Project In Visual Studio"&gt;&lt;br&gt;
&lt;small&gt;&lt;strong&gt;Figure 4&lt;/strong&gt; - Opening the project in Visual Studio lets you build and run more interactively&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;From here you can build (right click &lt;em&gt;Build&lt;/em&gt; or &lt;em&gt;Rebuild&lt;/em&gt;) or you can &lt;em&gt;Debug&lt;/em&gt; the project with the debugger using the green run button.&lt;/p&gt;
&lt;p&gt;In order for that to work we need one more file to configure how to 'launch' our Dll. Create a &lt;code&gt;launchsettings.json&lt;/code&gt; file that contains the startup options (you can also do this from &lt;strong&gt;Properties ? Debug ? Open Debug Launch Profiles Ui&lt;/strong&gt; but it's easier to create the file manually):&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-json"&gt;{
  &amp;quot;profiles&amp;quot;: {
    &amp;quot;VFP (Debug)&amp;quot;: {
      &amp;quot;commandName&amp;quot;: &amp;quot;Executable&amp;quot;,
      &amp;quot;executablePath&amp;quot;: &amp;quot;d:\\programs\\vfp9\\vfp9.exe&amp;quot;,    
      &amp;quot;workingDirectory&amp;quot;: &amp;quot;D:\\wwapps\\Conf\\wwDotnetBridgeRevisited\\Dotnet\\FirstLibrary&amp;quot;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This file specifies the path to the Visual FoxPro IDE (or if you prefer your application executable) and the folder that you want to start it in, which is your FoxPro project folder. From there you can then run your application or test program as I did in the example above.&lt;/p&gt;
&lt;h3 id="debugging"&gt;Debugging&lt;/h3&gt;
&lt;p&gt;For debugging it's important to understand that you are driving the debugging process through Visual Studio/.NET rather than through your FoxPro application. Visual Studio launches FoxPro or your application, and you then run the code that eventually hits the .NET code that you have a breakpoint on. But Visual Studio is in control of the process and FoxPro is just the target that actually calls into .NET code you are debugging.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that you can debug your own code .NET readily enough, but in most cases you can't debug external, system or third party .NET code unless debug information (&lt;code&gt;.pdb&lt;/code&gt;) file is provided by the vendor.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, with the Launch profile configured we can now run the application in Debug mode. When running in Debug mode you can now set breakpoints in your code, and the debugger then stops on those breakpoints:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://webconnectionblog.west-wind.com/imageContent/2025/CreatingAndDebuggingDotnetAssembliesForwwDotnetBridge/VisualStudioDebuggerBreakpoint.png" alt="Visual Studio Debugger Breakpoint"&gt;&lt;br&gt;
&lt;small&gt;&lt;strong&gt;Figure 5&lt;/strong&gt; - Stopped on a breakpoint in the Visual Studio Debugger. &lt;/small&gt;&lt;/p&gt;
&lt;p&gt;When on a breakpoint you can examine values using the Locals and Watch windows, but you can also hover over any active values or properties to display their values. From there you can continue to step over code using all the standard over, into, out step operations you're familiar with.&lt;/p&gt;
&lt;p&gt;Another nice feature: The debugger supports hot reload, which means you can make changes to some code while the app is running. If you've changed code that's already executed, you can move the execution pointer back to before the edit location and the code runs again with the newly compiled code. This can be a huge time saver when you're fixing code and trying to get it to work correctly.&lt;/p&gt;
&lt;p&gt;This example doesn't use any external libraries, but if you were interfacing with other libraries things work exactly the same. Again - debugging is limited to your own code in most cases.&lt;/p&gt;
&lt;p&gt;And that's it! With this you have all you need to build, run and debug your .NET components.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;VS Code also supports debugging and there's also a commandline debugger available. However, those debuggers only work with .NET Core and even then they don't work very well with an external process to attach to.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;If you're using wwDotnetBridge it's a good idea to take advantage of .NET as best as you can by avoiding excessive .NET code written from FoxPro code using wwDotnetBridge functions. If you're writing copious amounts of wwDotnetBridge code in FoxPro it's time to think about offloading that code into a .NET class or component that you can abstract and call from FoxPro with a simpler and much less chatty interface.&lt;/p&gt;
&lt;p&gt;Building .NET libraries is much easier than it used to be in days past, either using the no-IDE command line tooling, or if you want the IDE experience with a somewhat less intrusive Visual Studio installation. The tooling is drastically better and less intrusive to install than it used to be a few years ago.&lt;/p&gt;
&lt;p&gt;By using .NET code for more complex tasks you can make development of your code easier, and also improve performance by avoiding a chatty COM interop interface calls for every member access. While performance of wwDotnetBridge both with native COM calls and Reflection intrinsic method calls, doing those calls directly in .NET is still considerably faster.&lt;/p&gt;
&lt;p&gt;I highly recommend this approach on anything that exceeds a few lines of .NET code that you need to call from FoxPro. .NET code is compact and you can cram many different components into a single tiny DLL that you can ship with your application with little to no overhead. And since you're using wwDotnetBridge already anyway having one extra DLL is not an issue.&lt;/p&gt;
&lt;p&gt;Finally, by offloading some processing into .NET you have a chance to learn something new at your own pace. Because wwDotnetBridge interfaces deal with components you can create components large and small to move small chunks of your application to .NET that you can perhaps use in the future in other projects or if you decide to migrate in the future.&lt;/p&gt;
&lt;h2 id="resources"&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/RickStrahl/wwDotnetBridge"&gt;wwDotnetBridge Github Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.west-wind.com/wconnect/weblog/ShowEntry.blog?id=57032"&gt;wwDotnetBridge Revisited Article&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <link>https://webconnectionblog.west-wind.com/posts/2025/May/23/Creating-and-Debugging-Dotnet-Assemblies-for-wwDotnetBridge-and-Visual-FoxPro</link>
      <guid isPermaLink="false">57036</guid>
      <author> (Rick Strahl)</author>
      <comments>https://webconnectionblog.west-wind.com/posts/2025/May/23/Creating-and-Debugging-Dotnet-Assemblies-for-wwDotnetBridge-and-Visual-FoxPro#Comments</comments>
      <guid>https://webconnectionblog.west-wind.com/posts/2025/May/23/Creating-and-Debugging-Dotnet-Assemblies-for-wwDotnetBridge-and-Visual-FoxPro</guid>
      <pubDate>Fri, 23 May 2025 03:49:41 GMT</pubDate>
    </item>
    <item>
      <title>FoxPro Running on a Windows ARM Device</title>
      <description>I recently picked up a Windows ARM 'Co-Pilot' capable laptop and took it for a spin running my typical spread of Windows applications and tools. I also checked out how well it works with FoxPro - here's what I found.</description>
      <link>https://webconnectionblog.west-wind.com/posts/2024/Nov/07/FoxPro-Running-on-a-Windows-ARM-Device</link>
      <guid isPermaLink="false">57035</guid>
      <author> (Rick Strahl)</author>
      <comments>https://webconnectionblog.west-wind.com/posts/2024/Nov/07/FoxPro-Running-on-a-Windows-ARM-Device#Comments</comments>
      <guid>https://webconnectionblog.west-wind.com/posts/2024/Nov/07/FoxPro-Running-on-a-Windows-ARM-Device</guid>
      <pubDate>Thu, 07 Nov 2024 06:42:14 GMT</pubDate>
    </item>
    <item>
      <title>Web Connection 8.1 Release Post</title>
      <description>Web Connection 8.1 is out. This is a small maintenance release, but it does include a few significant updates.</description>
      <link>https://webconnectionblog.west-wind.com/posts/2024/Oct/14/Web-Connection-81-Release-Post</link>
      <guid isPermaLink="false">57034</guid>
      <author> (Rick Strahl)</author>
      <comments>https://webconnectionblog.west-wind.com/posts/2024/Oct/14/Web-Connection-81-Release-Post#Comments</comments>
      <guid>https://webconnectionblog.west-wind.com/posts/2024/Oct/14/Web-Connection-81-Release-Post</guid>
      <pubDate>Mon, 14 Oct 2024 19:29:02 GMT</pubDate>
    </item>
    <item>
      <title>Making Web Connection Work with Response Output Greater than 16mb</title>
      <description>Web Connection in the past has not supported &gt; 16mb direct output via plain string based output, due to FoxPro's 16mb string limit. 16mb is a lot of text and while I generally don't recommend returning that much data as part of non-file request (which does support larger files) it's a feature request that comes up from time to time as people overrun the limit. During last weekend's SW Fox conference I heard about this issue again in a session and decided to address it once and for all and this posts describes the change and how it's implemented.</description>
      <link>https://webconnectionblog.west-wind.com/posts/2024/Sep/30/Making-Web-Connection-Work-with-Response-Output-Greater-than-16mb</link>
      <guid isPermaLink="false">57033</guid>
      <author> (Rick Strahl)</author>
      <comments>https://webconnectionblog.west-wind.com/posts/2024/Sep/30/Making-Web-Connection-Work-with-Response-Output-Greater-than-16mb#Comments</comments>
      <guid>https://webconnectionblog.west-wind.com/posts/2024/Sep/30/Making-Web-Connection-Work-with-Response-Output-Greater-than-16mb</guid>
      <pubDate>Mon, 30 Sep 2024 22:30:22 GMT</pubDate>
    </item>
    <item>
      <title>wwDotnetBridge Revisited: An updated look at FoxPro .NET Interop</title>
      <description>In this very long white paper for the Southwest Fox conference, I discuss the basics of wwDotnetBridge and then demonstrate a variety of functionality with 10 examples. We'll see basic usage, how to wrap classes in FoxPro and .NET, how to use a variety of .NET 3rd party libraries, how to handle .NET events and how to make Task based async calls.</description>
      <link>https://webconnectionblog.west-wind.com/posts/2024/Sep/23/wwDotnetBridge-Revisited-An-updated-look-at-FoxPro-Dotnet-Interop</link>
      <guid isPermaLink="false">57032</guid>
      <author> (Rick Strahl)</author>
      <comments>https://webconnectionblog.west-wind.com/posts/2024/Sep/23/wwDotnetBridge-Revisited-An-updated-look-at-FoxPro-Dotnet-Interop#Comments</comments>
      <guid>https://webconnectionblog.west-wind.com/posts/2024/Sep/23/wwDotnetBridge-Revisited-An-updated-look-at-FoxPro-Dotnet-Interop</guid>
      <pubDate>Mon, 23 Sep 2024 04:34:21 GMT</pubDate>
    </item>
    <item>
      <title>West Wind Web Connection 8.0 Release Notes</title>
      <description>Web Connection 8.0 is here and this is the official release post for this new version.</description>
      <link>https://webconnectionblog.west-wind.com/posts/2024/Jun/25/West-Wind-Web-Connection-80-Release-Notes</link>
      <guid isPermaLink="false">9179</guid>
      <author> (Rick Strahl)</author>
      <comments>https://webconnectionblog.west-wind.com/posts/2024/Jun/25/West-Wind-Web-Connection-80-Release-Notes#Comments</comments>
      <guid>https://webconnectionblog.west-wind.com/posts/2024/Jun/25/West-Wind-Web-Connection-80-Release-Notes</guid>
      <pubDate>Tue, 25 Jun 2024 21:05:49 GMT</pubDate>
    </item>
    <item>
      <title>West Wind Client Tools 8.0 Release Notes</title>
      <description>West Wind Client Tools 8.0 has released as a major version rollup release. Here's all that's new and fixed.</description>
      <link>https://webconnectionblog.west-wind.com/posts/2024/May/30/West-Wind-Client-Tools-80-Release-Notes</link>
      <guid isPermaLink="false">9177</guid>
      <author> (Rick Strahl)</author>
      <comments>https://webconnectionblog.west-wind.com/posts/2024/May/30/West-Wind-Client-Tools-80-Release-Notes#Comments</comments>
      <guid>https://webconnectionblog.west-wind.com/posts/2024/May/30/West-Wind-Client-Tools-80-Release-Notes</guid>
      <pubDate>Thu, 30 May 2024 17:19:04 GMT</pubDate>
    </item>
    <item>
      <title>wwDotnetBridge and Loading Native Dependencies for .NET Assemblies</title>
      <description>If you're using a .NET component with wwDotnetBridge or plain COM Interop that has a native dependency on non-.NET DLLs, you need to be careful to ensure that the native libraries can be found. In this post I describe how .NET assembly loading works and how external native dependencies are resolved in .NET and subsequently how you have to deal with them in your FoxPro applications.</description>
      <link>https://webconnectionblog.west-wind.com/posts/2024/Mar/23/wwDotnetBridge-and-Loading-Native-Dependencies-for-Dotnet-Assemblies</link>
      <guid isPermaLink="false">9176</guid>
      <author> (Rick Strahl)</author>
      <comments>https://webconnectionblog.west-wind.com/posts/2024/Mar/23/wwDotnetBridge-and-Loading-Native-Dependencies-for-Dotnet-Assemblies#Comments</comments>
      <guid>https://webconnectionblog.west-wind.com/posts/2024/Mar/23/wwDotnetBridge-and-Loading-Native-Dependencies-for-Dotnet-Assemblies</guid>
      <pubDate>Sat, 23 Mar 2024 20:12:11 GMT</pubDate>
    </item>
  </channel>
</rss>