RESTful APIs

Topics: Developer Forum
Jan 30, 2012 at 4:21 PM

Hi. Does ActiveVFP has any included tool to create and implement a RESTful API ?  I just get working a probe-of-concept with my own code, but would like to know if is there any ready-to-use solution out there before I start to writing it on my own.

 

Thanks

 

Victor Espina

Jan 30, 2012 at 4:36 PM

Everything you need should be there, although I haven't done anything specifically with this in mind.  There's JSON and XML support (see http://activevfp.codeplex.com/discussions/276999)

I was looking over this article and I don't see any issues doing the same with AVFP: http://www.gen-x-design.com/archives/create-a-rest-api-with-php/

Jan 30, 2012 at 4:51 PM
Edited Jan 30, 2012 at 4:54 PM

I did read all the documentation and forum's posts and didn't found anything related to REST apis. I just wanted to confirm this before start "inventing the wheel".  At this time I've accomplished this:

 

a) I Add an IIS handler in order to redirect all requests without extension to the AVFP process

b) Add a CASE in the main DO CASE structure on MAIN.PRG to process "extension-less" requests:

CASE oProp.Ext == "."

       RETURN EXECSCRIPT(FILETOSTR(oProp.appStartPath + 'prg\rest\restHelper.prg'), oRequest)

 

This takes requests like 'http://mysite.com/rest/users?id=1' and automatically create an instance of "usersController" and call the "listAction" method, returning the response obtained from the method.  I now that the "canonical' REST syntax should be "/rest/users/1" but I couldn't get this to work in AVFP because how it works.

Now I will start to bulid a real life example using REST and ActiveVFP in order to probe the library works OK.  Once it is completed, how can I add this library to the ActiveVFP main distribution so anyone can take advantage of this ?

 

Regards

 

Victor Espina

Jan 30, 2012 at 5:25 PM
Edited Jan 30, 2012 at 5:42 PM

Sounds good!   Regarding "should be "/rest/users/1" but I couldn't get this to work in AVFP because how it works."

You can adjust how the URL works in 5.61 to have no extension at all so that anything processed on the URL will be processed the way you need it.

In Web.Config, find this section:
<handlers>
<add verb="*" path="*.avfp"
name="AVFPHandler"
type="AVFPHandler"/>
</handlers>

change to:

<handlers>
<add verb="*" path="*.*"
name="AVFPHandler"
type="AVFPHandler"/>
</handlers>

**From "Changing the extension scriptmap (AVFP5.61 only)" in the Documentation

Also, you might experiment with just something like http://localhost/yourvirtual/?/rest/users/1  which should default to the default page and still give you the string you need "/rest/users/1" to route properly.

or in 5.51:

http://localhost/yourvirtual/default.aspx/rest/users/1

Jan 30, 2012 at 5:52 PM
Edited Jan 30, 2012 at 5:54 PM

THe problem with the modified handler is that the AVFP process will try to load the main.prg file in the "folder" containing the resource. So, if I try to load this url: 

http://mysite.com/rest/users/1

AVFP will try to load /rest/users/main.prg instead of /prg/main.prg. So, this means that I will have to create a folder for each "controller" with its own copy a main.prg, and that's is not what I want.

What I thinking to do is to apply this custom convention:

REQUEST-METHOD controller [?params]

for example:

GET /users  --> Users.listAction()

GET /users?id=x --> Users.getAction()

POST /users --> Users.addAction()

POST /users?id=x --> Users.updAction()

DELETE /users --> Users.zapAction()

DELETE /users?id=x --> Users.dropAction()

GET /users?action=custom  --> Users.customAction()

(ANY) /users?action=get|list|add|upd|zap|drop&id=x --> 'action' parameters overrides intrinsic action

 

I think that could work pretty well. 

 

Regards

 

Victor Espina

Jan 30, 2012 at 6:11 PM

http://mysite.com/?rest/users/1

doesn't work??  Note the use of the ? question mark

Jan 30, 2012 at 7:46 PM

I haven't tried that form but, any case, I prefer the /rest/controller?param-list form.

Victor Espina

 

Jan 31, 2012 at 1:43 AM
Edited Jan 31, 2012 at 4:17 AM

I haven't experimented with Global.asax used with AVFP yet, but this is what ASP.NET MVC uses to do the routing of URLs.  You should be able to set up the URL any way you want...

I haven't yet seen why this wouldn't work with AVFP.   Extra kudos to you if you play with this...

Feb 2, 2012 at 1:47 PM

http://msdn.microsoft.com/en-us/magazine/dd347546.aspx

requires Microsoft .NET Framework 3.5 sp1 or above

you should try it with AVFP and let us know if it works

Feb 6, 2012 at 3:09 PM

Thanks for the article.  I think the problem is at lines 11 & 12 of proxystub.prg:

lcScript=FILETOSTR(oProp.AppStartPath+'\prg\main.prg')

lcHTMLout= EXECSCRIPT(lcScript) &&main()  && run the application

 

If we point to http://localhost/myapp/rest/controller?params, then oProp.AppStartPath will hold the value 'c:/mywebserver/myapp/rest/' and  proxystub.prg will try to execute a MAIN.PRG file located at PRG subfolder on AppStartPath, wich does not exists and therefore generates an error message. In order to allow a full REST implementation, proxystub should check if there is a MAIN.PRG at {appStartPath}\PRG folder and, if it's not, then try to call a MAIN.PRG located at {homeFolder}\PRG:

 

lcScript=FILETOSTR(oProp.AppStartPath+'\prg\main.prg')

IF NOT FILE(lcScript)

   lcScript = ADDBS(oRequest.serverVariables("APPL_PHYSICAL_PATH")) + "prg\main.prg"

ENDIF

lcHTMLout= EXECSCRIPT(lcScript) &&main()  && run the application

 

In this way, the MAIN.PRG located at the root folder of the webapp will always get called regardless the URL being requested.  This will also allow us to use the normal REST url syntax (/app/rest/controller/id).

 

Regards

 

Victor Espina

Feb 6, 2012 at 4:22 PM

Cool!  Have at it - this is your project will full source code so you can do anything you want with it.  Make it do whatever you want it to do. 

I'm just here to answer questions once and a while..

Feb 6, 2012 at 5:16 PM

I prefer not to mess with the sources of ActiveVFP. What I would like is that this change was made on the project's source code so the next version would have this enhance.

 

How can I do that?

 

Victor Espina

Feb 6, 2012 at 5:23 PM

The only way I'm putting out another release of AVFP is if AVFP doesn't work with Windows 8 or we find a MAJOR problem with the current implementation (which this is not).  So your only choice is to do this yourself or get someone else to do it for you.  You can "FORK" this project and use it to build something new too.  However, my contributions are over, except for answering questions once in a while and addressing significant issues.

This project officially belongs to the VFP community!

Feb 6, 2012 at 5:24 PM

Never mind. I allready found a way using Patchs posting.

Feb 6, 2012 at 5:40 PM

Ok. Suposse that I made all changes needed on ActiveVFP sources to allow an easy "out-of-the-box" implementation of RESTful APIs.  

a) I downloaded the sources codes in order to test if I could sucesfully recompile it, and I have 3 files missing: MAIN.PRG, MYTHREADFUNC.PRG and JSON.FLL

b) Once you provide me with the missing files and I sucessfully add the new features to ActiveVFP, how can I update the sources and installers in the CodePlex site?

 

Regards

Victor Espina

Feb 6, 2012 at 6:49 PM
Edited Feb 6, 2012 at 6:53 PM

a.) You already have those files with the regular download???

b.) You can start your own project or fork this one.

Or you can submit your change to the ISSUE TRACKER and the community can vote on the value of what you've contributed...    and if it gets enough votes I will add it.....

Feb 6, 2012 at 8:38 PM

a) No. I downloaded the source files TWICE and surely the files I mentioned are not included. Without them I can't recompile the project

b) Ok. I think it can work that way. I don't think is right to start another "MyActiveVFP" project that, essentially, is the same ActiveVFP with a few improves. 

 

Regards

Victor Espina

Feb 6, 2012 at 9:03 PM

a.) No I meant the regular ActiveVFP download without the source.  MAIN.PRG, MYTHREADFUNC.PRG and JSON.FLL are included with the regular download .

In other words, you download the source files into an existing ActiveVFP directory, then you have ALL files...

Feb 6, 2012 at 9:21 PM

a) No, I don't have that files in the normal download either. I used the ActiveVFP 5.61 Installer for IIS7... don't know if that has anything to do with this.

Feb 6, 2012 at 9:32 PM

a) I download the ZIP distribution and found the missing files there.  

 

Victor

Feb 6, 2012 at 9:37 PM

By the way, the installer DOES install the missing files. My mistake; I was looking at the wrong folder.

 

Victor

Feb 6, 2012 at 10:11 PM
Edited Feb 6, 2012 at 10:12 PM

it worked!! I made the change in proxystub.prg, recompiled the project, copied the new activevfp.dll to the appropiate location and voila!!! now I can point to /myserver/what/ever/url/I/want/to/request and its handled by a central MAIN.PRG :)

This is all I needed to create a mechanism to easily implement RESTful APIs with ActiveVFP and native VFP code!! I will let you know when the work is finished.

 

Thanks for your help

 

Victor Espina

Apr 22, 2012 at 8:17 AM
Edited Apr 22, 2012 at 8:23 AM

Victor,

Do you have a link for a demo we can see of your RESTful AVFP application?  Could you possibly zip up the application and upload here or send to me?

As I get more into the jQuery stuff, I am investigating RESTful and MVC, using AVFP, for the backend..

If need be, we can add this to a new release of ActiveVFP

Thanks!!

Apr 22, 2012 at 9:07 AM
Edited Apr 22, 2012 at 9:31 AM

Victor, regarding "a) I Add an IIS handler in order to redirect all requests without extension to the AVFP process".

could you please provide the source for this IIS Handler?  Is this really an ASP.NET handler??

Thanks!

(this is good stuff - I appreciate your work on this!!)

Apr 22, 2012 at 11:49 PM
Hi claude. I just received your email. I will try to answer you tomorrow.

Regards

Sent from my iPhone

On 22-04-2012, at 3:07, "claudefox" <notifications@codeplex.com> wrote:

From: claudefox

Victor, regarding "a) I Add an IIS handler in order to redirect all requests without extension to the AVFP process".

could you please provide the source for this IIS Handler? Is this really an ASP.NET handler??

Thanks!

Apr 23, 2012 at 9:01 AM

Great!  I added you as a developer and you'll get full credit for this RESTful version of AVFP...

Apr 23, 2012 at 5:33 PM

Claude, I came back today and toke a look on that I was doing with AVFP and rESTfull apis.  What I found is that I resolved the problem with the URL parsing, but the class miss the final implementation, i.e., call the appropiate method in the appropiate controller PRG.

Please give me a couple of days to work this out and then I will submit to you the changes for your approval.  About the ASP.NET Handler, what I did was to add an extra handler for "*" extensions, associated to AVFPHandler.  This will allow me to catch any request to a resource with no extension, wich is the case with RESTfull api calls.

 

Regards

Victor Espina

Apr 27, 2012 at 4:53 PM

Any updates?

Problems?

Apr 27, 2012 at 4:59 PM
Hi, no problems besides a lot of pending work! :)

I'm almost done, but still needs a little more time to get something you can test.

Regards

Victor Espina


On Fri, Apr 27, 2012 at 10:53 AM, claudefox <notifications@codeplex.com> wrote:

From: claudefox

Any updates?

Problems?

Read the full discussion online.

To add a post to this discussion, reply to this email (activevfp@discussions.codeplex.com)

To start a new discussion for this project, email activevfp@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe on CodePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at CodePlex.com




--
Victor Espina
Apr 30, 2012 at 1:00 AM
Hi ClaudeFox

I finally finished a draft version of a REST-like API implementation for ActiveVFP 5.61. The implementation is handled on PROXYSTUB.PRG, before passing the request to the MAIN.PRG. There I create an instance of a class called "restHelper" that is used to:

1) Check if the request is "REST-like"
2) Check if there is an available controller for the resource requested
3) Pass the request to the appropiate method in the resource's controller and return the HTML returned.

Controllers are implemented as PRG files located in a special folder inside the main "PRG" folder:

\PRG\Rest\Controllers


This PRG files should contain a class declaration like this:

DEFINE CLASS <resource-name>Controller AS restController
...
ENDDEFINE


The actions implemented by restController are the following:

infoAction (GET /resource/info)
helpAction (GET /resource/help)
listAction (GET /resource/)
getAction (GET /resource/id)
addAction (PUT /resource/)
updAction (PUT /resource/id)
dropAction (DELETE /resource/id)
zapAction (DELETE /resource/)


Please, download this file:
http://victorespina.com.ve/public/AVFP_REST_IMPLEMENTATION.rar

This file contains two folders:

bin: files to be copied to the ActiveVFP sources files in order to recompile it
source: files to test de REST implementation

What you need to do:
1) Copy the files located in BIN in your sources folder and then recompile ACTIVEVFP.DLL
2) Copy the files located in SOURCE in your test folder
3) Open a browser and point to http://your-avfp-app/customers/

Note that the SOURCE folder contains a modified WEB.CONFIG. This file is important because containes an aditional HANDLER required to handle the REST calls.


Hope it works! Any problems, please contact me via GoogleTalk with the address vespinas@gmail.com


Good luck

--
Victor Espina
Sent with Sparrow

On Friday, April 27, 2012 at 10:53 AM, claudefox wrote:

From: claudefox

Any updates?

Problems?

Read the full discussion online.

To add a post to this discussion, reply to this email (activevfp@discussions.codeplex.com)

To start a new discussion for this project, email activevfp@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe on CodePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at CodePlex.com


May 1, 2012 at 3:07 AM
Edited May 1, 2012 at 3:08 AM

Thank you so much!

I am working with this now.

2 things jump up that need to do to be consistent with current AVFP versions:

1.) End-User application logic needs to be in a prg as Procedural code and not, at least initially, be a class with methods.  The reason being that IIS caches these objects and doesn't allow a change in the code until the memory pool is recycled.  So, at least in development mode, it should be that way, imo.  (It should be optional for the developer to make it OO for performance reasons once the code is proven solid)   I don't see a problem implementing it this way, do you?

2.) The Views part of the M-V-C are already implemented in AVFP as .avfp files so that would continue with the app logic in the controllers and minimal app code in the Views.  I'll implement this with some examples.

I'm working on an ASP.NET MVC project right now so I may wait until that is finished to compare against this AVFP MVC. 

AVFP Version 6 (code name "AVFP MVC") is in the works!!

May 1, 2012 at 3:17 AM
Hi claudefox.

Be aware that my REST implementation is NOT a sustitution or equivalent of a full MVC implementation. I think the two can and SHOULD coexist so the programmer can decide wich way could be better for an specific project.

About procedural vs OOP, I indeed understand the problem. In fact, I had to kill w3wp.exe process every time I had to make a change in ACTIVEVFP.DLL or a controller PRG.

Let me try a couple of approaches to see if I can manage to keep de OOP syntax without loading the compiled code in memory as it does now.

I also want to make a change so the controller can be informed about the content-type indicated in the request. This way, you can create a controller capable of returing data in XML, JSON, HTML o XHTML as demanded.

Regards

Victor Espina


On Mon, Apr 30, 2012 at 9:07 PM, claudefox <notifications@codeplex.com> wrote:

From: claudefox

Thank you so much!

I am working with this now.

2 things jump up that need to do to be consistent with current AVFP versions:

1.) End-User application logic needs to be in a prg as Procedural code and not, at least initially, be a class with methods. The reason being that IIS caches these objects and doesn't allow a change in the code until the memory pool is recycled. So, at least in development mode, it should be that way, imo. (It should be optional to make it OO for performance reasons once the code is proven solid) I don't see a problem implementing it this way, do you?

2.) The Views part of the M-V-C are already implemented in AVFP as .avfp files so that would continue with the app logic in the controllers and minimal app code in the Views. I'll implement this with some examples.

I'm working on an ASP.NET MVC project right now so I may wait until that is finished to compare against this AVFP MVC.

AVFP Version 6 (code name "AVFP MVC") is in the works!!

Read the full discussion online.

To add a post to this discussion, reply to this email (activevfp@discussions.codeplex.com)

To start a new discussion for this project, email activevfp@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe on CodePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at CodePlex.com




--
Victor Espina
May 1, 2012 at 3:19 AM
By the way, it would be nice to have a demo AVFP page in the main distribution wich show how to return data using the JSON parser. That would be very helpfull.

Regards

Victor Espina


On Mon, Apr 30, 2012 at 9:17 PM, Victor Espina <vespinas@gmail.com> wrote:
Hi claudefox.

Be aware that my REST implementation is NOT a sustitution or equivalent of a full MVC implementation. I think the two can and SHOULD coexist so the programmer can decide wich way could be better for an specific project.

About procedural vs OOP, I indeed understand the problem. In fact, I had to kill w3wp.exe process every time I had to make a change in ACTIVEVFP.DLL or a controller PRG.

Let me try a couple of approaches to see if I can manage to keep de OOP syntax without loading the compiled code in memory as it does now.

I also want to make a change so the controller can be informed about the content-type indicated in the request. This way, you can create a controller capable of returing data in XML, JSON, HTML o XHTML as demanded.

Regards

Victor Espina


On Mon, Apr 30, 2012 at 9:07 PM, claudefox <notifications@codeplex.com> wrote:

From: claudefox

Thank you so much!

I am working with this now.

2 things jump up that need to do to be consistent with current AVFP versions:

1.) End-User application logic needs to be in a prg as Procedural code and not, at least initially, be a class with methods. The reason being that IIS caches these objects and doesn't allow a change in the code until the memory pool is recycled. So, at least in development mode, it should be that way, imo. (It should be optional to make it OO for performance reasons once the code is proven solid) I don't see a problem implementing it this way, do you?

2.) The Views part of the M-V-C are already implemented in AVFP as .avfp files so that would continue with the app logic in the controllers and minimal app code in the Views. I'll implement this with some examples.

I'm working on an ASP.NET MVC project right now so I may wait until that is finished to compare against this AVFP MVC.

AVFP Version 6 (code name "AVFP MVC") is in the works!!

Read the full discussion online.

To add a post to this discussion, reply to this email (activevfp@discussions.codeplex.com)

To start a new discussion for this project, email activevfp@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe on CodePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at CodePlex.com




--
Victor Espina



--
Victor Espina
May 1, 2012 at 3:23 AM
Claudefox, there is some implementation of master pages in AVFP currently? If not, I will love to try to implement a basic approach of this. I LOVE to create string-parsing-and-processing code with VFP, and I think that ASP.NET master-pages concept are very usefull, even in a MVC environment.

Regards

Victor Espina


On Mon, Apr 30, 2012 at 9:19 PM, Victor Espina <vespinas@gmail.com> wrote:
By the way, it would be nice to have a demo AVFP page in the main distribution wich show how to return data using the JSON parser. That would be very helpfull.

Regards

Victor Espina


On Mon, Apr 30, 2012 at 9:17 PM, Victor Espina <vespinas@gmail.com> wrote:
Hi claudefox.

Be aware that my REST implementation is NOT a sustitution or equivalent of a full MVC implementation. I think the two can and SHOULD coexist so the programmer can decide wich way could be better for an specific project.

About procedural vs OOP, I indeed understand the problem. In fact, I had to kill w3wp.exe process every time I had to make a change in ACTIVEVFP.DLL or a controller PRG.

Let me try a couple of approaches to see if I can manage to keep de OOP syntax without loading the compiled code in memory as it does now.

I also want to make a change so the controller can be informed about the content-type indicated in the request. This way, you can create a controller capable of returing data in XML, JSON, HTML o XHTML as demanded.

Regards

Victor Espina


On Mon, Apr 30, 2012 at 9:07 PM, claudefox <notifications@codeplex.com> wrote:

From: claudefox

Thank you so much!

I am working with this now.

2 things jump up that need to do to be consistent with current AVFP versions:

1.) End-User application logic needs to be in a prg as Procedural code and not, at least initially, be a class with methods. The reason being that IIS caches these objects and doesn't allow a change in the code until the memory pool is recycled. So, at least in development mode, it should be that way, imo. (It should be optional to make it OO for performance reasons once the code is proven solid) I don't see a problem implementing it this way, do you?

2.) The Views part of the M-V-C are already implemented in AVFP as .avfp files so that would continue with the app logic in the controllers and minimal app code in the Views. I'll implement this with some examples.

I'm working on an ASP.NET MVC project right now so I may wait until that is finished to compare against this AVFP MVC.

AVFP Version 6 (code name "AVFP MVC") is in the works!!

Read the full discussion online.

To add a post to this discussion, reply to this email (activevfp@discussions.codeplex.com)

To start a new discussion for this project, email activevfp@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe on CodePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at CodePlex.com




--
Victor Espina



--
Victor Espina



--
Victor Espina
May 1, 2012 at 4:36 AM

The JSON example is in there already.  Here's the demo:  http://thetechconsult.com/demo/default.aspx?action=zipsearch

and here's the code:

CASE oProp.Action=="jsonresults"  
   
    #DEFINE MAX_RESULTS 100        && Maximum number of zip codes that this script will return
    LOCAL AjaxResponse, SearchZip
    oJSON=NEWOBJECT('json','json.prg')  && Craig Boyd's JSON implementation
    SearchZip = oRequest.QueryString("zip") && Read the search string from the query string
    AjaxResponse = CREATEOBJECT("Empty")  && Create an object, which will be serialized to JSON format in our response
    ADDPROPERTY(AjaxResponse,"Zip", SearchZip)  &&Add a property that will indicate whether the script returned search results
    AddProperty(AjaxResponse,"HasResults", .F.)  &&Add a property that will indicate whether the script returned search results
    AddProperty(AjaxResponse,"ResultsCount", 0)&&Add a property that will indicate the number of matching zip codes
    IF NOT EMPTY(SearchZip)
        SELECT * FROM ZipCodes ;
            WHERE zip LIKE SearchZip + "%" ;
            ORDER BY zip ;
            INTO CURSOR SearchResults
        AjaxResponse.ResultsCount = RECCOUNT()
    ELSE
        AjaxResponse.ResultsCount = 0
    ENDIF
    IF BETWEEN(AjaxResponse.ResultsCount, 1, MAX_RESULTS)
        AjaxResponse.HasResults = .T.
        oJSON.keyforcursors="zipcodes"
    ENDIF
    * send JSON data and properties back
    oResponse.ContentType = "text/plain"
    oResponse.Write(oJSON.AddJSONProps(oJSON.stringify(AjaxResponse),oJSON.stringify('SearchResults')))
    oResponse.Flush
    lcHTMLout=[]
  

May 1, 2012 at 4:42 AM

The current AVFP Views (.avfp files) have similar functionality to Master Pages but are more similar to how it's done in PHP or even Classic ASP.  The INCLUDE function is used to include centralized parts on any page.  For example in all the .avfp pages you'll see an Include("header.avfp") at the top and an Include("footer.avfp") at the bottom but they could be anything and are not limited to Top and Bottom only...

May 1, 2012 at 5:12 AM
Great! Thanks

--
Victor Espina
Sent with Sparrow

On Monday, April 30, 2012 at 10:36 PM, claudefox wrote:

From: claudefox

The JSON example is in there already. Here's the demo: http://thetechconsult.com/demo/default.aspx?action=zipsearch

and here's the code:

CASE oProp.Action=="jsonresults"   
    
    #DEFINE MAX_RESULTS 100        && Maximum number of zip codes that this script will return
    LOCAL AjaxResponse, SearchZip
    oJSON=NEWOBJECT('json','json.prg')  && Craig Boyd's JSON implementation
    SearchZip = oRequest.QueryString("zip") && Read the search string from the query string
    AjaxResponse = CREATEOBJECT("Empty")  && Create an object, which will be serialized to JSON format in our response
    ADDPROPERTY(AjaxResponse,"Zip", SearchZip)  &&Add a property that will indicate whether the script returned search results
    AddProperty(AjaxResponse,"HasResults", .F.)  &&Add a property that will indicate whether the script returned search results
    AddProperty(AjaxResponse,"ResultsCount", 0)&&Add a property that will indicate the number of matching zip codes
    IF NOT EMPTY(SearchZip)
        SELECT * FROM ZipCodes ;
            WHERE zip LIKE SearchZip + "%" ;
            ORDER BY zip ;
            INTO CURSOR SearchResults
        AjaxResponse.ResultsCount = RECCOUNT()
    ELSE
        AjaxResponse.ResultsCount = 0
    ENDIF
    IF BETWEEN(AjaxResponse.ResultsCount, 1, MAX_RESULTS)
        AjaxResponse.HasResults = .T.
        oJSON.keyforcursors="zipcodes"
    ENDIF
    * send JSON data and properties back
    oResponse.ContentType = "text/plain"
    oResponse.Write(oJSON.AddJSONProps(oJSON.stringify(AjaxResponse),oJSON.stringify('SearchResults')))
    oResponse.Flush
    lcHTMLout=[]
  

Read the full discussion online.

To add a post to this discussion, reply to this email (activevfp@discussions.codeplex.com)

To start a new discussion for this project, email activevfp@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe on CodePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at CodePlex.com


May 1, 2012 at 5:14 AM
I understand the INCLUDE functionality, but that is not the same as MasterPages. This is because a INCLUDE funcion inserts a common chunk of HTML in the CURRENT layout, while a MasterPage inserts the CURRENT html in a COMMON layout. Do you see the difference?



--
Victor Espina
Sent with Sparrow

On Monday, April 30, 2012 at 10:42 PM, claudefox wrote:

From: claudefox

The current AVFP Views (.avfp files) have similar functionality to Master Pages but are more similar to how it's done in PHP or even Classic ASP. The INCLUDE function is used to include centralized parts on any page. For example in all the .avfp pages you'll see an Include("header.avfp") at the top and an Include("footer.avfp") at the bottom but they could be anything and are not limited to Top and Bottom only...

Read the full discussion online.

To add a post to this discussion, reply to this email (activevfp@discussions.codeplex.com)

To start a new discussion for this project, email activevfp@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe on CodePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at CodePlex.com


May 1, 2012 at 11:16 AM

Yes, I see the difference.  And I encourage you to pursue an AVFP Masterpage implementation.

I purposely went the "Include" route in the basic AVFP installation to mimic PHP and WordPress functionality.  PHP is arguably the most popular server-side web programming language in the world because of its ease of use, I think.  I am trying to capture that whole ease of use thing in AVFP as a contrast to a lot of what is in ASP.NET.  But I am open to new and different ways of doing it based on the value of the technique as well...

May 1, 2012 at 11:34 AM
Hi. I share your view of "keep it simple", but one of the things I like it the most in vfp is its ability of giving you more than one way to do what you want to do.

The include technique is great and I had used it a lot in php in the past, but also loved the clean and fast way to make complex project "templates" that give you asp.net with its master page approach, that is applicable even with the new mvc approach.

I will try it and let you know how it goes.

Regards

Victor Espina


Sent from my iPhone

On 01-05-2012, at 5:16, "claudefox" <notifications@codeplex.com> wrote:

From: claudefox

Yes, I see the difference. And I encourage you to pursue an AVFP Masterpage implementation.

I purposely went the "Include" route in the basic AVFP installation to mimic PHP and WordPress functionality. PHP is arguably the most popular server-side web programming language in the world because of its ease of use, I think. I am trying to capture that whole ease of use thing in AVFP as a contrast to a lot of what is in ASP.NET. But I am open to new and different ways of doing it based on the value of the technique as well...

May 1, 2012 at 1:35 PM
Edited May 1, 2012 at 1:40 PM

FWIW, Brian Marquis did a MVC implementation called FoxTrails that is similar to ActiveVFP because it uses ASP.NET as a bridge to use VFP code using a vfp mtdll. I can send you the Foxtrails.zip if you're interested. One thing he does that might be very useful here is the concept of 'clearing an instance' of the server to allow the developer to use CreateObject or SET LIB TO without locking it in IIS.  This would be useful to the AVFP developer creating controllers in an OO way which is probably the preferred way to do it.  And this would probably be a worthy addition to AVFP 6.0

Anyway,  the following Foxtrails code is the equivalent to AVFP's Proxystub.prg but would allow controllers to use CreateObject the normal way (by CLEARing the instance) without needing the App pool recycled when a change is made:

 

dispatch = NEWOBJECT("dispatcher","dispatch.prg")
IF TYPE("pcUrl") <> "U"
    WITH dispatch.oDispatchContext
        .request = pcDC.request
        .session = pcDC.session
        .response = pcDC.response
        .server = pcDC.server
    ENDWITH
    SET STEP ON
    RETURN dispatch.dispatchrequest(pcUrl,pcFilePath)
ENDIF

DEFINE CLASS Dispatcher AS Session OLEPUBLIC
   
    oDispatchContext = ""
    autocompile = .f.
    lredirect = .f.
    lUseCache = .f.
   
    PROCEDURE Init()
   
        LOCAL lcHome, lcPath
       
        *-- set the initial environment
        SET EXCLUSIVE OFF
        SET TALK OFF
        SET DELETED ON
        SET REPROCESS TO -1
        SET MULTILOCKS ON
       
        IF Application.Startmode = 3 OR Application.StartMode = 5
           lcHome = ADDBS(JUSTPATH(Application.ServerName))
        ELSE
           lcHome = SYS(5) + ADDBS(CURDIR())
        ENDIF
        CD "&lcHome"
        lcPath = SET("PATH")
        IF ! "Utility" $ lcPath
            SET PATH TO ("Utility;"+lcPath)
        ENDIF
        This.oDispatchContext = NEWOBJECT("DispatchContext")
       
    ENDPROC
   
    PROCEDURE DispatchRequest( lcUrl As String, lcFilePath )
        LOCAL arg, lcArgList
        PRIVATE oDispatchContext, oDispatch
        oDispatchContext = this.oDispatchContext
        oDispatch = this
       
        This.ParseUrl(lcUrl,lcFilePath)
        This.CreateInstance()
        oDispatchContext.Instance.GetSession()
        oDispatchContext.Instance.OpenTables()
        This.ProcessQueryString()
        WITH This.oDispatchContext
            .Result = .instance.AfterInit()
            IF !This.lRedirect AND LEN(.method) > 0
                IF !isnull(.arguments[1])
                    lcArgList = "("
                    FOR EACH arg IN .arguments
                        IF TYPE("arg") = "L"
                           LOOP
                        ENDIF
                        arg = urldecode(arg)
                        IF LEN(lcArgList )>1
                            lcArgList = lcArgList + ","
                        ENDIF
                        lcArgList = lcArgList + '"'+arg+'"'
                    ENDFOR
                    lcArgList = lcArgList + ")"
                ELSE
                    lcArgList = "()"
                ENDIF
                .Result = EVALUATE(".instance." + .method + lcArgList)
            ENDIF
            IF !This.lRedirect
                IF FILE("Views/"+.Controller+"/"+.RenderAction+".vml")
                    .renderedView = RenderVml("Views/"+.Controller+"/"+.RenderAction+".vml",.Instance,this.lUseCache)
                    This.oDispatchContext.html = .renderedView
                ENDIF
                IF FILE("Layouts/"+.RenderLayout+".vml")
                    .renderedLayout = RenderVml("Layouts/"+.RenderLayout+".vml",.Instance,this.lUseCache)
                    This.oDispatchContext.html = .renderedLayout
                ENDIF
            ENDIF
            *-- release our instance so we can compile fxp's on the fly.
            .Instance = null
            CLEAR CLASS (.Controller)
            CLEAR PROGRAM (.Controller)
            CLEAR RESOURCES
            CLOSE DATABASES
        ENDWITH
        RETURN This.oDispatchContext.html
    ENDPROC
   
    HIDDEN PROCEDURE ParseUrl(lcUrl,lcFilePath)
        LOCAL lnPos

        WITH this.oDispatchContext
            .url = lcUrl
            if "?" $ .url
                IF ISNULL(.request)
                    .queryString = SUBSTR(.url, AT("?",.url)+1)
                ENDIF
                .url = LEFT(.url, AT("?",.url)-1)
            endif
            IF "//" $ .url
                .domain = SUBSTR(.url,AT("//",.url)+2)
                .domain = LEFT(.domain,AT("/",.domain)-1)
            ENDIF
            lnPos = AT(JUSTFNAME(LOWER(lcFilePath)),LOWER(.url))
            .BaseUrl = LEFT(.url,AT(lcFilePath,.url)-1)+lcFilePath
            * get asp page
            IF lnPos > 0
                .ASPPage = SUBSTR(.Url,lnPos)
                lnPos = AT("/",.ASPPage)
            ENDIF
            * get controller
            IF lnPos > 0
                .controller = SUBSTR(.ASPPage,lnPos+1)
                .ASPPage= LEFT(.ASPPage,lnPos-1)
                lnPos = AT("/",.controller)
            ENDIF
            IF LEN(.controller) = 0
                .controller = "default"
            ENDIF
           
            * get method
            IF lnPos > 0
                .method = SUBSTR(.controller,lnPos+1)
                .controller = LEFT(.controller,lnPos-1)
                lnPos = AT("/",.method)
            ELSE
                .method = "index"
            ENDIF
           
            * parameters
            IF lnPos > 0
                ALINES(.arguments,SUBSTR(.method,lnPos+1),4,"/")
                .method = LEFT(.method,lnPos-1)
            ELSE
                .arguments[1] = null
            ENDIF

            .RenderLayout = .Controller
            .RenderAction = .Method       

        ENDWITH
    ENDPROC
   
    HIDDEN PROCEDURE CreateInstance()
        WITH this.oDispatchContext
            IF this.autocompile
                *% RSJ - Try compiling controller PRG if newer than FXP
                lnFiles=ADIR(laController,FULLPATH("Controllers\"+.controller+".*"))
                ltPrgTime = DATETIME()
                ltFxpTime = ltPrgTime
                llFxpExists = .F.
                FOR lnI = 1 TO lnFiles
                            DO CASE
                            CASE ".PRG" $ UPPER(laController[lnI,1])
                                        ltPrgTime = CTOT(DTOC(laController[lnI,3])+" "+laController[lnI,4])
                            CASE ".FXP" $ UPPER(laController[lnI,1])
                                        llFxpExists = .T.
                                        ltFxpTime = CTOT(DTOC(laController[lnI,3])+" "+laController[lnI,4])                                             
                            ENDCASE
                NEXT lnI
                IF (ltPrgTime > ltFxpTime) OR NOT llFxpExists
                            TRY
                                        COMPILE (FULLPATH("Controllers\"+.controller+".prg"))
                            CATCH
                                        STRTOFILE("Error: "+MESSAGE(),"compile_error.txt")
                            ENDTRY
                ENDIF
            ENDIF
            .instance = NEWOBJECT(.controller,FULLPATH("Controllers\"+.controller+".prg"))
        ENDWITH
    ENDPROC

    PROCEDURE ProcessQueryString()
        LPARAMETERS pcKey
        LOCAL lcExact, lcKey, lcValue
        LOCAL ARRAY laVars[1], laPair[2]
        lcExact = SET("Exact")
        SET EXACT OFF
        IF PCOUNT() = 0
         pcKey = ""
        ELSE
         pcKey = LOWER(pckey)
        ENDIF
        WITH This.oDispatchContext
            IF ISNULL(.Request)
                IF !EMPTY(.queryString)
                    laVars[1] = .queryString
                    ALINES(laVars,.queryString,0,"&")
                    FOR EACH lcPair IN laVars
                        ALINES(laPair,lcPair,1,"=")
                        IF laPair[1] = pcKey
                            lcKey = this.getKey(laPair[1])
                            IF ALEN(laPair) = 2
                                lcValue = urldecode(laPair[2])
                            ELSE
                                lcValue = ""
                            ENDIF
                            .Instance.AddProperty(lcKey,this.CoerceValue(laPair[1],lcValue))
                        ENDIF
                    ENDFOR
                ENDIF
                RETURN
            ENDIF
            IF LEN(pcKey) > 0
                lcValue = this.CoerceValue(pcKey,.Session.Value(pcKey))
                IF !ISNULL(lcValue)
                    .Instance.AddProperty(this.getKey(pcKey),lcValue)
                ENDIF
            ENDIF
            FOR EACH lcKey  IN .Request.QueryString
                IF LOWER(lcKey) = pcKey
                    .Instance.AddProperty(this.getKey(lcKey),this.CoerceValue(lcKey,.Request.QueryString(lcKey).Item()))
                ENDIF
            ENDFOR
            FOR EACH lcKey  IN .Request.Form
                IF LOWER(lcKey) = pcKey
                    .Instance.AddProperty(this.getKey(lcKey),this.CoerceValue(lcKey,.Request.Form(lcKey).Item()))
                ENDIF
            ENDFOR
        ENDWITH
    ENDPROC

    PROCEDURE getKey()
        LPARAMETERS lcKey
        DO CASE
        CASE LEN(lcKey)=0
            lcKey = "_"
        CASE LEFT(lcKey,1) = "_"
            lcKey = SUBSTR(lcKey,3)
        CASE LEFT(lcKey,2) = "._"
            IF LEN(lcKey) = 2
                lcKey = "_"
            ELSE
                lcKey = "_"+SUBSTR(lcKey,3)
            ENDIF
        CASE LEFT(lcKey,1) $ "~!@$%^*."
            IF LEN(lcKey) = 1
                lcKey = "_"
            ELSE
                lcKey = "_" + this.GetKey(SUBSTR(lcKey,2))
            ENDIF
        ENDCASE
        RETURN lcKey
    ENDPROC
   
    PROCEDURE CoerceValue()
        LPARAMETERS lcKey,lcValue
        LOCAL lcType,lcField,lcValue
        IF LEFT(lcKey,1) = "_"
            lcType = UPPER(SUBSTR(lcKey,2,1))
        ELSE
            lcType = "C"
        ENDIF
        DO CASE
        CASE lcType = "T"
            lcValue = CTOT(lcValue)
        CASE lcType = "D"
            lcValue = CTOD(lcValue)
        CASE lcType = "L"
            lcValue = INLIST(UPPER(lcValue),"Y","T",".T.","1","-1","TRUE")
        CASE lcType $ "NYI"
            lcValue = VAL(lcValue)
        OTHERWISE
            lcValue = TRIM(lcValue)
        ENDCASE
        RETURN lcValue
    ENDPROC

    PROCEDURE Error
        LPARAMETERS nError, cMethod, nLine
        LOCAL lcError
        lcError = ""
        TEXT TO lcError TEXTMERGE NOSHOW PRETEXT 3
            <> (Error <> at line <>)
<> ENDTEXT This.AddError(lcError) RETURN ENDPROC PROCEDURE AddError( ErrMsg ) LOCAL numErrors IF ISNULL(This.oDispatchContext.Errors[1]) numErrors = 1 ELSe numErrors = ALEN( This.oDispatchContext.Errors ) + 1 ENDIF DIMENSION this.oDispatchContext.errors[ numErrors ] this.oDispatchContext.errors[ numErrors ] = ErrMsg STRTOFILE(ErrMsg+CHR(10),"foxtrails.err",1) ENDPROC PROCEDURE DebugRequest( lcUrl As String, lcFilePath ) LOCAL loFox, lcReturn lcReturn = "" loFox = GETOBJECT(,"visualfoxpro.application.9") loFox.SetVar("pcUrl",@lcUrl) loFox.SetVar("pcFilePath",@lcFilePath) loFox.SetVar("pcDC",this.oDispatchContext) lcReturn = loFox.eval("dispatch()") RELEASE loFox RETURN lcReturn ENDPROC ENDDEFINE DEFINE CLASS DispatchContext As Relation URL = "" BaseUrl = "" ASPPage = "" Controller = "" Method = "" RenderLayout = "" renderedLayout = "" renderAction = "" renderedView = "" Html = "" domain = "" queryString = "" Result = null Instance = null MTS = null Request = null Response = null Session = null Server = null DIMENSION arguments[1] DIMENSION errors[1] PROCEDURE Init This.ResetContext() TRY this.mts = CREATEOBJECT("MTxAS.AppServer.1") IF !ISNULL(this.mts) this.request = this.mts.getObjectContext().Item("Request") this.response = this.mts.getObjectContext().Item("Response") this.session = this.mts.getObjectContext().Item("Session") this.server = this.mts.getObjectContext().Item("Server") ENDIF CATCH TO loException ENDTRY ENDPROC PROCEDURE ResetContext This.URL = "" This.BaseUrl = "" This.ASPPage = "" This.Controller = "" This.Method = "" This.RenderLayout = "" This.renderedLayout = "" This.renderAction = "" This.renderedView = "" This.Html = "" This.Result = null This.Instance = null This.MTS = null This.Request = null This.Response = null This.Session = null This.Server = null DIMENSION arguments[1] DIMENSION errors[1] This.arguments[1] = null this.errors[1] = null ENDPROC PROCEDURE hasErrors() RETURN ! ISNULL(this.errors[1]) ENDPROC PROCEDURE setMimeType() LPARAMETERS lcMimeType IF !ISNULL(This.Response) This.Response.ContentType = lcMimeType ENDIF ENDPROC ENDDEFINE
May 1, 2012 at 1:46 PM
Thanks I will check this code ASAP

Regards


Sent from my iPhone

On 01-05-2012, at 7:35, "claudefox" <notifications@codeplex.com> wrote:

From: claudefox

FWIW, Brian Marquis did a MVC implementation called FoxTrails that is similar to ActiveVFP because it uses ASP.NET as a bridge to use VFP code. I can send you the Foxtrails.zip if you're interested. One thing he does that might be very useful here is the concept of 'clearing an instance' of the server to allow the developer to use CreateObject or SET LIB TO without locking it in IIS. This would be useful to the AVFP developer creating controllers in an OO way which is probably the preferred way to do it. And this would probably be a worthy addition to AVFP 6.0

Anyway, the following Foxtrails code is the equivalent to AVFP's Proxystub.prg but would allow controllers to use CreateObject the normal way (by CLEARing the instance) without needing the App pool recycled when a change is made:

dispatch = NEWOBJECT("dispatcher","dispatch.prg")
IF TYPE("pcUrl") <> "U"
    WITH dispatch.oDispatchContext
        .request = pcDC.request
        .session = pcDC.session
        .response = pcDC.response
        .server = pcDC.server
    ENDWITH
    SET STEP ON
    RETURN dispatch.dispatchrequest(pcUrl,pcFilePath)
ENDIF

DEFINE CLASS Dispatcher AS Session OLEPUBLIC
   
    oDispatchContext = ""
    autocompile = .f.
    lredirect = .f.
    lUseCache = .f.
   
    PROCEDURE Init()
   
        LOCAL lcHome, lcPath
       
        *-- set the initial environment
        SET EXCLUSIVE OFF
        SET TALK OFF
        SET DELETED ON
        SET REPROCESS TO -1
        SET MULTILOCKS ON
       
        IF Application.Startmode = 3 OR Application.StartMode = 5
           lcHome = ADDBS(JUSTPATH(Application.ServerName))
        ELSE
           lcHome = SYS(5) + ADDBS(CURDIR())
        ENDIF
        CD "&lcHome"
        lcPath = SET("PATH")
        IF ! "Utility" $ lcPath
            SET PATH TO ("Utility;"+lcPath)
        ENDIF
        This.oDispatchContext = NEWOBJECT("DispatchContext")
       
    ENDPROC
   
    PROCEDURE DispatchRequest( lcUrl As String, lcFilePath )
        LOCAL arg, lcArgList
        PRIVATE oDispatchContext, oDispatch
        oDispatchContext = this.oDispatchContext
        oDispatch = this
       
        This.ParseUrl(lcUrl,lcFilePath)
        This.CreateInstance()
        oDispatchContext.Instance.GetSession()
        oDispatchContext.Instance.OpenTables()
        This.ProcessQueryString()
        WITH This.oDispatchContext
            .Result = .instance.AfterInit()
            IF !This.lRedirect AND LEN(.method) > 0
                IF !isnull(.arguments[1])
                    lcArgList = "("
                    FOR EACH arg IN .arguments
                        IF TYPE("arg") = "L"
                           LOOP
                        ENDIF
                        arg = urldecode(arg)
                        IF LEN(lcArgList )>1
                            lcArgList = lcArgList + ","
                        ENDIF
                        lcArgList = lcArgList + '"'+arg+'"'
                    ENDFOR
                    lcArgList = lcArgList + ")"
                ELSE
                    lcArgList = "()"
                ENDIF
                .Result = EVALUATE(".instance." + .method + lcArgList)
            ENDIF
            IF !This.lRedirect
                IF FILE("Views/"+.Controller+"/"+.RenderAction+".vml")
                    .renderedView = RenderVml("Views/"+.Controller+"/"+.RenderAction+".vml",.Instance,this.lUseCache)
                    This.oDispatchContext.html = .renderedView
                ENDIF
                IF FILE("Layouts/"+.RenderLayout+".vml")
                    .renderedLayout = RenderVml("Layouts/"+.RenderLayout+".vml",.Instance,this.lUseCache)
                    This.oDispatchContext.html = .renderedLayout
                ENDIF
            ENDIF
            *-- release our instance so we can compile fxp's on the fly.
            .Instance = null
            CLEAR CLASS (.Controller)
            CLEAR PROGRAM (.Controller)
            CLEAR RESOURCES
            CLOSE DATABASES
        ENDWITH
        RETURN This.oDispatchContext.html
    ENDPROC
   
    HIDDEN PROCEDURE ParseUrl(lcUrl,lcFilePath)
        LOCAL lnPos

        WITH this.oDispatchContext
            .url = lcUrl
            if "?" $ .url
                IF ISNULL(.request)
                    .queryString = SUBSTR(.url, AT("?",.url)+1)
                ENDIF
                .url = LEFT(.url, AT("?",.url)-1)
            endif
            IF "//" $ .url
                .domain = SUBSTR(.url,AT("//",.url)+2)
                .domain = LEFT(.domain,AT("/",.domain)-1)
            ENDIF
            lnPos = AT(JUSTFNAME(LOWER(lcFilePath)),LOWER(.url))
            .BaseUrl = LEFT(.url,AT(lcFilePath,.url)-1)+lcFilePath
            * get asp page
            IF lnPos > 0
                .ASPPage = SUBSTR(.Url,lnPos)
                lnPos = AT("/",.ASPPage)
            ENDIF
            * get controller
            IF lnPos > 0
                .controller = SUBSTR(.ASPPage,lnPos+1)
                .ASPPage= LEFT(.ASPPage,lnPos-1)
                lnPos = AT("/",.controller)
            ENDIF
            IF LEN(.controller) = 0
                .controller = "default"
            ENDIF
           
            * get method
            IF lnPos > 0
                .method = SUBSTR(.controller,lnPos+1)
                .controller = LEFT(.controller,lnPos-1)
                lnPos = AT("/",.method)
            ELSE
                .method = "index"
            ENDIF
           
            * parameters
            IF lnPos > 0
                ALINES(.arguments,SUBSTR(.method,lnPos+1),4,"/")
                .method = LEFT(.method,lnPos-1)
            ELSE
                .arguments[1] = null
            ENDIF

            .RenderLayout = .Controller
            .RenderAction = .Method       

        ENDWITH
    ENDPROC
   
    HIDDEN PROCEDURE CreateInstance()
        WITH this.oDispatchContext
            IF this.autocompile
                *% RSJ - Try compiling controller PRG if newer than FXP
                lnFiles=ADIR(laController,FULLPATH("Controllers\"+.controller+".*"))
                ltPrgTime = DATETIME()
                ltFxpTime = ltPrgTime
                llFxpExists = .F.
                FOR lnI = 1 TO lnFiles
                            DO CASE
                            CASE ".PRG" $ UPPER(laController[lnI,1])
                                        ltPrgTime = CTOT(DTOC(laController[lnI,3])+" "+laController[lnI,4])
                            CASE ".FXP" $ UPPER(laController[lnI,1])
                                        llFxpExists = .T.
                                        ltFxpTime = CTOT(DTOC(laController[lnI,3])+" "+laController[lnI,4])                                             
                            ENDCASE
                NEXT lnI
                IF (ltPrgTime > ltFxpTime) OR NOT llFxpExists
                            TRY
                                        COMPILE (FULLPATH("Controllers\"+.controller+".prg"))
                            CATCH
                                        STRTOFILE("Error: "+MESSAGE(),"compile_error.txt")
                            ENDTRY
                ENDIF
            ENDIF
            .instance = NEWOBJECT(.controller,FULLPATH("Controllers\"+.controller+".prg"))
        ENDWITH
    ENDPROC

    PROCEDURE ProcessQueryString()
        LPARAMETERS pcKey
        LOCAL lcExact, lcKey, lcValue
        LOCAL ARRAY laVars[1], laPair[2]
        lcExact = SET("Exact")
        SET EXACT OFF
        IF PCOUNT() = 0
         pcKey = ""
        ELSE
         pcKey = LOWER(pckey)
        ENDIF
        WITH This.oDispatchContext
            IF ISNULL(.Request)
                IF !EMPTY(.queryString)
                    laVars[1] = .queryString
                    ALINES(laVars,.queryString,0,"&")
                    FOR EACH lcPair IN laVars
                        ALINES(laPair,lcPair,1,"=")
                        IF laPair[1] = pcKey
                            lcKey = this.getKey(laPair[1])
                            IF ALEN(laPair) = 2
                                lcValue = urldecode(laPair[2])
                            ELSE
                                lcValue = ""
                            ENDIF
                            .Instance.AddProperty(lcKey,this.CoerceValue(laPair[1],lcValue))
                        ENDIF
                    ENDFOR
                ENDIF
                RETURN
            ENDIF
            IF LEN(pcKey) > 0
                lcValue = this.CoerceValue(pcKey,.Session.Value(pcKey))
                IF !ISNULL(lcValue)
                    .Instance.AddProperty(this.getKey(pcKey),lcValue)
                ENDIF
            ENDIF
            FOR EACH lcKey  IN .Request.QueryString
                IF LOWER(lcKey) = pcKey
                    .Instance.AddProperty(this.getKey(lcKey),this.CoerceValue(lcKey,.Request.QueryString(lcKey).Item()))
                ENDIF
            ENDFOR
            FOR EACH lcKey  IN .Request.Form
                IF LOWER(lcKey) = pcKey
                    .Instance.AddProperty(this.getKey(lcKey),this.CoerceValue(lcKey,.Request.Form(lcKey).Item()))
                ENDIF
            ENDFOR
        ENDWITH
    ENDPROC

    PROCEDURE getKey()
        LPARAMETERS lcKey
        DO CASE
        CASE LEN(lcKey)=0
            lcKey = "_"
        CASE LEFT(lcKey,1) = "_"
            lcKey = SUBSTR(lcKey,3)
        CASE LEFT(lcKey,2) = "._"
            IF LEN(lcKey) = 2
                lcKey = "_"
            ELSE
                lcKey = "_"+SUBSTR(lcKey,3)
            ENDIF
        CASE LEFT(lcKey,1) $ "~!@$%^*."
            IF LEN(lcKey) = 1
                lcKey = "_"
            ELSE
                lcKey = "_" + this.GetKey(SUBSTR(lcKey,2))
            ENDIF
        ENDCASE
        RETURN lcKey
    ENDPROC
   
    PROCEDURE CoerceValue()
        LPARAMETERS lcKey,lcValue
        LOCAL lcType,lcField,lcValue
        IF LEFT(lcKey,1) = "_"
            lcType = UPPER(SUBSTR(lcKey,2,1))
        ELSE
            lcType = "C"
        ENDIF
        DO CASE
        CASE lcType = "T"
            lcValue = CTOT(lcValue)
        CASE lcType = "D"
            lcValue = CTOD(lcValue)
        CASE lcType = "L"
            lcValue = INLIST(UPPER(lcValue),"Y","T",".T.","1","-1","TRUE")
        CASE lcType $ "NYI"
            lcValue = VAL(lcValue)
        OTHERWISE
            lcValue = TRIM(lcValue)
        ENDCASE
        RETURN lcValue
    ENDPROC

    PROCEDURE Error
        LPARAMETERS nError, cMethod, nLine
        LOCAL lcError
        lcError = ""
        TEXT TO lcError TEXTMERGE NOSHOW PRETEXT 3
            <> (Error <> at line <>)
<> ENDTEXT This.AddError(lcError) RETURN ENDPROC PROCEDURE AddError( ErrMsg ) LOCAL numErrors IF ISNULL(This.oDispatchContext.Errors[1]) numErrors = 1 ELSe numErrors = ALEN( This.oDispatchContext.Errors ) + 1 ENDIF DIMENSION this.oDispatchContext.errors[ numErrors ] this.oDispatchContext.errors[ numErrors ] = ErrMsg STRTOFILE(ErrMsg+CHR(10),"foxtrails.err",1) ENDPROC PROCEDURE DebugRequest( lcUrl As String, lcFilePath ) LOCAL loFox, lcReturn lcReturn = "" loFox = GETOBJECT(,"visualfoxpro.application.9") loFox.SetVar("pcUrl",@lcUrl) loFox.SetVar("pcFilePath",@lcFilePath) loFox.SetVar("pcDC",this.oDispatchContext) lcReturn = loFox.eval("dispatch()") RELEASE loFox RETURN lcReturn ENDPROC ENDDEFINE DEFINE CLASS DispatchContext As Relation URL = "" BaseUrl = "" ASPPage = "" Controller = "" Method = "" RenderLayout = "" renderedLayout = "" renderAction = "" renderedView = "" Html = "" domain = "" queryString = "" Result = null Instance = null MTS = null Request = null Response = null Session = null Server = null DIMENSION arguments[1] DIMENSION errors[1] PROCEDURE Init This.ResetContext() TRY this.mts = CREATEOBJECT("MTxAS.AppServer.1") IF !ISNULL(this.mts) this.request = this.mts.getObjectContext().Item("Request") this.response = this.mts.getObjectContext().Item("Response") this.session = this.mts.getObjectContext().Item("Session") this.server = this.mts.getObjectContext().Item("Server") ENDIF CATCH TO loException ENDTRY ENDPROC PROCEDURE ResetContext This.URL = "" This.BaseUrl = "" This.ASPPage = "" This.Controller = "" This.Method = "" This.RenderLayout = "" This.renderedLayout = "" This.renderAction = "" This.renderedView = "" This.Html = "" This.Result = null This.Instance = null This.MTS = null This.Request = null This.Response = null This.Session = null This.Server = null DIMENSION arguments[1] DIMENSION errors[1] This.arguments[1] = null this.errors[1] = null ENDPROC PROCEDURE hasErrors() RETURN ! ISNULL(this.errors[1]) ENDPROC PROCEDURE setMimeType() LPARAMETERS lcMimeType IF !ISNULL(This.Response) This.Response.ContentType = lcMimeType ENDIF ENDPROC ENDDEFINE