5/12/2008

installing a Subversion server on Windows

Update: This Guide is now largely obsolete, because Brian wrote an installer that will do all this stuff for you. Check out hisannoucement*, or go straight to the svn1clicksetup project page on tigris.

Subversion sounds pretty cool. It’s a mature, powerful revision-control system that acts a lot like CVS, adds support for atomic commits and real renames, just won the Jolt award, and is free. What more can you ask for?

I’ve been intending to install Subversion for quite a while, but I kept putting it off, because it looked like a daunting task. But when I actually decided to go do it, it took me all of an hour and a half to get it installed and working. If somebody had just written down what I needed to do to set up Subversion on Windows, with a real server running as a real Windows service, then it probably would’ve only taken me ten minutes, and I would’ve done it weeks ago.

Here, then, is the Mere-Moments Guide to installing a Subversion server on Windows. (It may look a bit intimidating, but really, it’s not.)

Some quick notes on the Guide:

  • These instructions assume you’re using Windows 2000 or XP. (You’d better be; the Subversion server won’t run on Win9x.)
  • If you want to know more about Subversion than just how to install it, check out the free O’Reilly Subversion book online and the not-free Pragmatic Version Control using Subversion.
  • For Subversion to do you much good, you’ll have to add a new “project” (essentially a directory) to your repository, to put files in. In these instructions, I’m assuming that your new project will be called monkey(because mine was).
  • Feel free to skip steps and to play around; you’ll learn more that way, because things won’t work right and you’ll have to figure out why.

And now, on to the Guide.

  1. Download everything
    1. Go to http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=91 and download the most recent svn-x.y.z-setup.exe. At the time of this writing, the latest version was svn-1.2.0-setup.exe.
    2. Go to http://dark.clansoft.dk/~mbn/svnservice/ and download SVNService.zip.
    3. Go to http://tortoisesvn.tigris.org/download.html and download the most recent installer. At the time of this writing, the latest version was TortoiseSVN-1.1.7-UNICODE_svn-1.1.4.msi. (It doesn’t have to be the exact same version as the svn installer you got in step 1. See the compatibility chart.)
  2. Install the server and the command-line client
    1. Run svn-x.y.z-setup.exe and let it install stuff.
    2. Go to Control Panel > System, go to the Advanced tab, and click the “Environment Variables” button at the bottom. Click the “New” button (either one, but if you’re undecided, use the one under “System variables”), set “variable name” to SVN_EDITOR, and “variable value” to the path and filename of a text editor of your choice (e.g., C:\Windows\Notepad.exe). OK all the way out.
  3. Create a repository and configure access
    1. Create a new directory somewhere out of the way; this is where your repository will live, but you’ll almost never actually open the files directly. I made a directory called svn_repos directly under my C:\Documents and Settings, just so it’d be out of the way.
    2. Open a command prompt and type: svnadmin create “C:\Documents and Settings\svn_repos”
    3. In Windows Explorer, browse to the C:\Documents and Settings\svn_repos\conf directory (which svnadmin just created for you), and edit a couple of config files:
      1. Open the svnserve.conf file in a text editor, and uncomment the [general]anon-access = readauth-access = write, and password-db = passwd lines. Save.
      2. Open the passwd file in a text editor, uncomment the [users] line, and add the username and password you want to use when connecting to your subversion server. Save.
  4. Start the server manually, and create a project
    1. In your command window, type: svnserve –daemon –root “C:\Documents and Settings\svn_repos”
    2. Open a second command window, and type svn mkdir svn://localhost/monkey
    3. You’ll see the text editor you specified in step II.2, with some text already in it. Type a comment, like “Created the monkey project”, at the beginning of the file (before the line starting with “–”). Save the file and close the editor.
    4. If your Subversion login is the same as your Windows login, then type your password (the one you put in the passwd file) at the prompt, and hit Enter. If your Subversion login is different from your Windows login, then just hit ENTER at the password prompt, and Subversion will then ask for both your login and your password.
    5. Subversion should tell you that it “Committed revision 1.” Congratulations! You just checked a change into Subversion. Throw yourself a party. (Yes, creating a directory is a revisioned change — you can go back and get the repository as of a time before that directory existed. This is novel stuff for folks like me who still use VSS at work.)
    6. It’s conventional to have /trunk, /branches, and /tags subdirectories for each project (your code goes into trunk, and the others are where you put, well, branches and tags). Go ahead and type svn mkdir svn://localhost/monkey/trunk (and notice that, after you enter a checkin comment, it doesn’t prompt you for your password again — it’s smart like that).
  5. Start the server for real
    1. Go back to the command window that’s running svnserve. Hit Ctrl+C to stop it.
    2. Open the SVNService.zip that you downloaded earlier. Extract SVNService.exe into your Subversion bin directory (Program Files\Subversion\bin). Yes, it’s important that you put it in this directory; it has to be in the same place as svnserve.exe from the Subversion distribution.
    3. In a command prompt, type svnservice -install –daemon –root “C:\Documents and Settings\svn_repos”
    4. Go to Control Panel > Administrative Tools > Services, double-click the SVNService service, and change its startup type from “Manual” to “Automatic”. Now Subversion will start every time you start Windows.
    5. Start the SVNService service (by selecting it in the Services list, and clicking the “play” toolbar button).
    6. Go back to a command prompt, and type svn ls svn://localhost/
      This will list all the files in the root of the repository. If all is well and you’ve got a real Subversion server running now, you should see: monkey/
  6. Install TortoiseSVN
    Sure, you can get by with a command-line client, but TortoiseSVN is cool — it integrates Subversion into Windows Explorer. You get little overlay icons showing the status of each file (in sync, needs to be checked in, not yet in the repository, etc.), and you can do pretty much everything you need by right-clicking on files and folders.

    1. Run the TortoiseSVN installer you got back in part I.
    2. Create a monkey directory somewhere on your hard drive. Right-click somewhere in that folder and select “SVN Checkout…” Type svn://localhost/monkey/trunk/ for the repository URL and click OK.
    3. Create a file in that directory, any file. Right-click the file and select TortoiseSVN > Add. Notice the little plus-sign icon that appears.
      The file hasn’t actually been checked in yet — Subversion’s commits are both batched and atomic, so this new file, together with any other new files you added, any files you changed, any files you deleted, any files you renamed, any directories you added or deleted or renamed, will all show up on the server all at once, as a single revision and a single checkin, the next time you right-click and select “SVN Commit”.
  7. Make it run on the network
    Are you kidding? You’re already networked. Go to another computer on your LAN, install TortoiseSVN, and do an “SVN Checkout…”. When you specify the repository URL, use the same URL you did before, but replace “localhost” with the actual name of the computer that’s running the Subversion service (so in my case, the repository URL is svn://marsupial/monkey/trunk/ — nice little menagerie, there).

And there ya go — Subversion up and running on Windows, in mere moments or less.

jQuery AJAX calls to a WCF REST Service

Since I've posted a few jQuery posts recently I've gotten a bunch of feedback to have more content on using jQuery in Ajax scenarios and showing some examples on how to use jQuery to cut out ASP.NET Ajax. In this post I'll show how you can use jQuery to call a WCF REST service without requiring the ASP.NET AJAX ScriptManager and the client scripts that it loads by default. Note although I haven't tried it recently the same approach should also work with ASMX style services.

WCF 3.5 includes REST functionality and one of the features of the new WCF webHttp binding is to return results in a variety of ways that are URL accessible. WCF has always supported plain URL HTTP access, but it's not been real formal and had somewhat limited functionality as parameters had to be encodable as query string parameters. With the webHttp binding there's now an official WCF protocol geared towards providing ASP.NET AJAX JSON compatibility (using WebScript behavior) as well of a slightly cleaner raw JSON implementation (basic webHttp binding).

You can return XML (default), JSON or raw data from WCF REST services. Regardless of content type, natively WCF always wants to return content in a 'wrapped' format which means that both inbound parameters and outbound results are wrapped into an object.

Let's take a look at the message format for a REST JSON service method.

[ServiceContract(Name="StockService",Namespace="JsonStockService")]    
public interface IJsonStockService
{
    [OperationContract]          
    [WebInvoke(Method="POST",
               BodyStyle=WebMessageBodyStyle.Wrapped,
               ResponseFormat=WebMessageFormat.Json
    )]
    StockQuote GetStockQuote(string symbol);

..

The input message on the wire looks like this:

{"symbol":"MSFT"}

The response looks like this:

{"GetStockQuoteResult":
        {"Company":"MICROSOFT CP",
        "LastPrice":30.00,
        "LastQuoteTime":
        "\/Date(1208559600000-0700)\/",
        "LastQuoteTimeString":"Apr 18, 4:00PM",
        "NetChange":0.78,
        "OpenPrice":29.99,
        "Symbol":"MSFT"}
}

Notice that in both cases an object is used. For the inbound data all parameters are wrapped into an object and rather than just passing the value, the name of the parameter becomes a property in the JSON object map that gets sent to the server. This is actually quite useful - if you're just sending a raw JSON structure you could only pass a single parameter to the server - and that option is also available via the Web BodyStyle=WebMessageBodyStyle.Bare option on the service method.

The outbound result set is also wrapped into an object which is a lot less useful. This is a hold over from WCF which wraps all responses into a message result object, which usually makes sense in order to support multiple result values (ie. out parameters etc.). In a Web scenario however this doesn't really buy you much. Nevertheless if you want to pass multiple parameters to the server you have to use this wrapped format along with the result value.

Calling with jQuery

If you're using jQuery and you'd like to call a WCF REST service it's actually quite easy either with bare or wrapped messages. Bare messages are easier to work with since they skip the wrapping shown above, but as I mentioned you're limited to a single input parameter. So if your service has any complexity you'll likely want to use wrapped messages.

You can opt to either call services using the ASP.NET Ajax logic (WebScriptService behavior) or using the raw service functionality which is shown above.

To call these methods with jQuery is fairly straight forward in concept - jQuery includes both low level and highlevel methods that can call a URL and return JSON data. The two methods available are $.getJSON() which automatically parses result JSON data and $.ajax(), which is a lower level function that has many options for making remote calls and returning data.

getJSON() is useful for simple scenarios where the server returns JSON, but it doesn't allow you to pass JSON data TO the server. The only way to send data to the server with getJSON is via query string or POST data that is sent as standard POST key/value pairs. In all but the simplest scenarios getJSON() is not all that useful.

The lower level $.ajax method is more flexible, but even so it still lacks the capability to pass JSON data TO the server. So little extra work and some external JSON support is required to create JSON output on the client as well as dealing with Microsoft Ajax's date formatting.

Personally I prefer to use a wrapper method for making JSON calls to the server to encapsulate this functionality. Note although this method seems somewhat lengthy it deals with a few important issues that you need to take care of when calling WCF REST Services:

// *** Service Calling Proxy Class
function serviceProxy(serviceUrl)
{
    var _I = this;
    this.serviceUrl = serviceUrl;
 
    // *** Call a wrapped object
    this.invoke = function(method,data,callback,error,bare)
    {
        // *** Convert input data into JSON - REQUIRES Json2.js
        var json = JSON2.stringify(data); 
 
        // *** The service endpoint URL        
        var url = _I.serviceUrl + method;
 
        $.ajax( { 
                    url: url,
                    data: json,
                    type: "POST",
                    processData: false,
                    contentType: "application/json",
                    timeout: 10000,
                    dataType: "text",  // not "json" we'll parse
                    success: 
                    function(res) 
                    {                                    
                        if (!callback) return;
 
                        // *** Use json library so we can fix up MS AJAX dates
                        var result = JSON2.parse(res);
 
                        // *** Bare message IS result
                        if (bare)
                        { callback(result); return; }
 
                        // *** Wrapped message contains top level object node
                        // *** strip it off
                        for(var property in result)
                        {
                            callback( result[property] );
                            break;
                        }                    
                    },
                    error:  function(xhr) {
                        if (!error) return;
                        if (xhr.responseText)
                        {
                            var err = JSON2.parse(xhr.responseText);
                            if (err)
                                error(err); 
                            else    
                                error( { Message: "Unknown server error." })
                        }
                        return;
                    }
                });   
    }
}
// *** Create a static instance
var Proxy = new serviceProxy("JsonStockService.svc/");

WCF services are called by their URL plus the methodname appended in the URL's extra path, so here:

JsonStockService.svc/GetStockQuote

is the URI that determines the service and method that is to be called on it.

The code above uses the core jQuery $.ajax() function which is the 'low level' mechanism for specifying various options. Above I'm telling it to accept raw string input (in JSON format), convert the response from JSON into an object by evaling the result, as well as specifying the content type and timeout. Finally a callback handler and error callback are specified.

Note that I override the success handler here to factor out the wrapped response object so that the value received in the callback handler is really only the result and not the wrapped result object. More on this in a second.

The call for the above StockQuote(symbol) call looks like this (including some app specific code that uses the result data):

var symbol = $("#txtSymbol").val();            
Proxy.invoke("GetStockQuote",{ symbol: symbol },
    function (result)
    {   
        //var result = serviceResponse.GetStockQuoteResult;
 
        $("#StockName").text( result.Company + " (" + result.Symbol + ")" ) ;
        $("#LastPrice").text(result.LastPrice.toFixed(2));
        $("#OpenPrice").text(result.OpenPrice.toFixed(2));
        $("#QuoteTime").text(result.LastQuoteTimeString); 
        $("#NetChange").text(result.NetChange.toFixed(2));   
 
        // *** if hidden make visible
        var sr = $("#divStockQuoteResult:hidden").slideDown("slow");
 
        // *** Also graph it
        var stocks = [];
        stocks.push(result.Symbol);
        var url = GetStockGraphUrl(stocks,result.Company,350,150,2);                
        $("#imgStockQuoteGraph").attr("src",url);
    },
    onPageError);

Parameters are passed in as { parm1: "value1", parm2: 120.00 } etc. - you do have to know the parameter names as parameters are matched by name not position.

The result is returned to the inline callback function in the code above and that code assigns the StockQuote data into the document. Notice that the result returned to the callback function is actually NOT a wrapped object. The top level object has been stripped off so the wrapper is not there anymore.

If you look at the the ajaxJSON function, you can see that it looks for the first result property in the actual object that WCF returns and uses IT to call the callback function instead - so it's indirect routing. This saves you from the one line of code commented out above and having to know exactly what that Result message name is ( WCF uses Result). Not that one line of code would kill you, but it's definitely cleaner and more portable.

The same approach should also work with ASMX style services BTW which uses the same messaging format.

JSON encoding

Note that the ajaxJSON function requires JSON encoding. jQuery doesn't have any native JSON encoding functionality (which seems a big omission, but was probably done to preserve the small footprint). However there are a number of JSON implementations available. Above I'm using the JSON2.js file from Douglas Crockford to serialize the parameter object map into JSON.

There's another wrinkle though: Date formatting. Take another look at the stock quote returned from WCF:

{"GetStockQuoteResult":
        {"Company":"MICROSOFT CP",
        "LastPrice":30.00,
        "LastQuoteTime":
        "\/Date(1208559600000-0700)\/",
        "LastQuoteTimeString":"Apr 18, 4:00PM",
        "NetChange":0.78,
        "OpenPrice":29.99,
        "Symbol":"MSFT"}
}

There's no JavaScript date literal and Microsoft engineered a custom date format that is essentially a marked up string. The format is a string that's encoded and contains the standard new Date(milliseconds since 1970) value. But the actual type of the date value in JSON is a string. If you use standard JSON converters the value will be returned as a string exactly as you see it above. I've talked about the date issues, and hacking existing JSON implementations before. I've modified Crockford's JSON2.JS to support the Microsoft date format so it properly encodes in and outbound data. You can download the hacked JSON2_MsDates.zip if you're interested. You can look at the code to see the modifications that were required, which essentially amounts to pre filtering parsed data before evaling on the .toJSON end and dropping the Data format that Date.prototype.toJSON() produces and instead creating a string in the required format above when doing object encoding.

Bare Messages

If you want a cleaner message format and you're content with single parameter inputs to functions then the WebMessageBodyStyle.Bare can work for you. Bare gives you a single JSON parameter you can pass that is automatically mapped to the first and only parameter of a method. You can't use Bare with any service methods (other than GET input) that include more than one parameter - the service will throw an exception when you access any method (beware: it's a RUNTIME error!).

Bare messages are easier to work with but they are limited because of the single parameter. You can use a single parameter on the server and make that input a complex type like an array to simulate multiple parameters. Using input objects or arrays can work for this. While this works realize that WCF requires an exact type match so any input 'wrapper' types you create yourself have to be mappable to a .NET type.

My first instinct with WCF's web bindings was always to use Bare, but ultimately the wrapped format provides more flexibility even if it is a little uglier on the wire. For AJAX services wrapped seems to make more sense.

Ideally, I would have preferred even more control - wrapped input messages and bare output messages, but I guess you can't have everything ...

Other Input Alternatives

Passing JSON messages is one thing you can do - the other option is to pass raw POST variables, which is something that can be done natively with jQuery without requiring a JSON encoder. Basically jQuery allows you to specify data as an object map, and it can turn the object into regular encoded POST parameters.

[OperationContract]          
[WebInvoke(Method="POST",
           BodyStyle=WebMessageBodyStyle.Bare,
           ResponseFormat=WebMessageFormat.Json
 )]
StockQuote GetStockQuote(string symbol);

You'd also need to mark your class:

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class JsonStockService : StockServiceBase

and enable ASP.NET compatibility in web.config (see my WCF REST Configuration Post for details)

If you prefer the simplified logic and you can use POST input data (which works well if you rather post back to a handler or the same ASPX page) you can get away with the following:

function ajaxJsonPost(method,data,callback,error)
{
    var url = "JsonStockService.svc/" + method;
    $.ajax( { 
                url: url,
                data: data,
                type: "POST",
                processData: true,
                contentType: "application/json",
                timeout: 10000,
                dataType: "json",
                success: callback,
                error: error
            });   
}

When you send data like this you can actually change the message format to Bare and get just a raw object response. jQuery can either except a raw POST string for the data parameter or an object whose properties and values are turned into POST key value pairs.

If you want to use POST behavior with WCF though, you need to enable ASPNET Compatibility on the REST service - otherwise the HttpContext.Current.Request is not available since WCF REST by default is trying to be host agnostic. For more information on how to configure WCF REST services check my previous post on WCF REST configuration last week.

This format might be preferrable if you are indeed building a public API that will be externally accessed. Raw POST data interchange is more common for many Ajax libraries, and also lends it self to straight HTTP clients that don't have JSON encoding features built in. For public APIs this makes plenty of sense. Remember that if you care about date formatting you may want to add the explicit JSON2 parsing code into the success callback (I left this out here for simplicities sake).

Error Handling

One more issue you'll want to be very careful of with WCF REST Services when you're using non-WebScriptService (ASP.NET AJAX style) behavior: When an error occurs WCF unfortunately throws an HTML error page rather than a JSON or XML fault message. That's a big problem if you want to return meaningful error messages to your client application - the only way to retrieve the error is by parsing the messy and very bulky HTML document returned.I've tried finding some way to get the REST services to throw a full JSON based error message and I haven't found a way to do this. JSON error messages seem to only work when you're using WebScriptService which is the full ASP.NET AJAX emulation. Under WebScriptService behavior the message returns the standard Exception like structure that includes a .Message and .StackTrace property that lets you echo back errors more easily.

In the end this means that even if you are using a non-MS Ajax client it might be the best solution to use the ASP.NET AJAX style WebHttp binding, simply because it provides the behavior that you most commonly require. There's nothing lost by doing so. You don't incur any client ASP.NET AJAX client requirements, but you do get the wrapped format input and exceptions properly wrapped on errors, plus this format is easier to implement because it doesn't require any special attributes on each individual operation/method as it's a fixed format. On the downside you do lose the ability to use UrlTemplates which might be useful in some situations, but it's probably not a common scenario that you need this for pure AJAX services.

Passing a JSON object to a WCF service with jQuery

This example uses WCF to create a service endpoint that will be accessible via an ASP.NET page with jQuery/AJAX. We will use AJAX to pass a JSON object from the client-side to the webservice. We will only use jQuery to connect to the web service, there will be no ASP.NET AJAX library used. Why no ASP.NET AJAX library? jQuery is already included in the project and it can handle all the necessary AJAX calls and functionality that we would want if we were using the ASP.NET AJAX script library. We're also going to save about 80kb of overhead (much more if in debug mode) by excluding the ASP.NET AJAX library. This is in no way saying that the ASP.NET AJAX library isn't useful... As a matter of fact if we were to do the same example with the library we could save ourselves from writing extra code. However the point of this example is to show that we can access the web service even if we don't have a nicely generated client-side proxy a la ASP.NET AJAX.

The WCF Service:

I'm going to start by adding an AJAX-enabled WCF Service to a Website. (Make sure you're running the correct version of .NET - I am using 3.5 here)

After adding the service it opens up to the service's code-behind file. Go ahead and browse around the file for a second.

The first thing I'm going to point out is to make sure that the "AspNetCompatibilityRequirements" is set to "Allowed":

[code:c#]
[AspNetCompatibilityRequirements( RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed )]
[/code]

This attribute indicates that our service should run in ASP.NET compatibility mode. If it were not "Allowed" we would not be able to access the service from ASP.NET. This attribute is automatically generated when you add the "AJAX-enabled WCF Service." For a detailed explanation of the attribute go to MSDN.

Looking at the generated code-behind file we can see it has placed a "DoWork()" method with the "OperationContract" attribute. This is created by default but lets keep it since we will be using this method to run this example. One thing we want to add is a "WebGet" attribute and set the "RequestFormat" to "Json." WebGet associates the operation with a UriTemplate (not discussed in this example) as well as the GET verb. Setting the RequestFormat allows us to define that the Request should be in JSON format. Our "DoWork()" method should now look like this:

[code:c#]
[OperationContract]
[WebGet( RequestFormat=WebMessageFormat.Json )]
public void DoWork()
{
// Add your operation implementation here
return;
}
[/code]



The Data/Object Structure:

We want to pass in a "Person" object to the "DoWork()" method so lets quickly create a Person object with properties for a Name, Age and the types of Shoes they own (first thing that popped into my head). This class will also serve as the structure for our JSON object.

[code:c#]
[Serializable]
[DataContract( Namespace = "http://www.dennydotnet.com/", Name = "Person" )]
public class Person
{
private string _name = string.Empty;
private int _age = 0;

[DataMember( IsRequired = true, Name = "Name" )]
public string Name
{
get { return _name; }
set { _name = value; }
}

[DataMember( IsRequired = true, Name = "Age" )]
public int Age
{
get { return _age; }
set { _age = value; }
}

[DataMember( IsRequired = true, Name = "Shoes" )]
public List Shoes;

}
[/code]

We've decorated our Person class as a DataContract specifying the Namespace and Name. We've also decorated our properties with a DataMember attribute. We've set "IsRequired" for each one to true and specified the Name. You really only need to specify the "Name" if it's going to be different than the property name. For example you could have a property named "Level" and the DataMember attribute's Name set to "Rank." We can now go back and modify our "DoWork()" method to receive a Person object as a param. It should now look like the following:

[code:c#]
[OperationContract]
[WebGet( RequestFormat=WebMessageFormat.Json )]
public void DoWork(Person p)
{
// Add your operation implementation here
return;
}
[/code]

The Web.Config File:

You'll need to make a few changes to your web.config file before you can access your service. You'll need to add a serviceBehavior to allow httpGet and we'll also add some helpful debugging options too. Add the following to your web.config:

Below

[code:xml]
<serviceBehaviors>
<behavior name="ServiceAspNetAjaxBehavior">
<serviceMetadata httpGetEnabled="true" httpGetUrl="" />
<serviceDebug httpHelpPageEnabled="true" includeExceptionDetailInFaults="true" />
behavior>
serviceBehaviors>
[/code]


Between [here] your service node should look like this:
[code:xml]
<service name="Service" behaviorConfiguration="ServiceAspNetAjaxBehavior">
<endpoint address="" behaviorConfiguration="ServiceAspNetAjaxBehavior"
binding="webHttpBinding" contract="Service" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
service>
[/code]

A security note about the following line:

[code:xml]
<serviceDebug httpHelpPageEnabled="true" includeExceptionDetailInFaults="true" />
[/code]


Allowing exception details can expose internal application information including personally identifiable or otherwise sensitive information. Setting the option to true is only recommended as a way to temporarily debug your service!!

Your Web.Config should look like the following: (pardon the colors)

The Front-End:

Now that the service is created and configured we can move to the front-end (make sure jQuery.js is included in your ASP.NET page). First let's create a sample JSON object that we will pass to the service. We'll create the JSON object based on the structure of the Person class.

[code:js]
var mydata = { "Name":"Denny", "Age":23, "Shoes":["Nike","Osiris","Etnies"] };
[/code]


If you're not too familiar with JSON this is what our JSON object looks like as an object (JsonViewer):

We need to somehow communicate with the WCF service and since we're using jQuery we can use the library's built-in AJAX methods. The code below creates an AJAX call. the headers are set to GET and the contentType is application/json. We set the url to the path to our WCF service's svc file with a trailing / and then the name of the method we want to execute. In this case we're calling the "DoWork()" method. "data" will be passed in to our function and processData should be set to false so that jquery does not try to auto-process our data. We've also added a success and error function to let us know what happens after executing the AJAX.

[code:js]
function sendAJAX(data) {
$.ajax({
type: "GET",
contentType: "application/json",
url: "Service.svc/DoWork",
data: data,
processData: false,
success:
function(msg){
alert( "Data Saved!" );
},
error:
function(XMLHttpRequest, textStatus, errorThrown){
alert( "Error Occured!" );
}
});
}
[/code]

Now unfortunately there is a small issue here. We must send the actual JSON string as the value for DoWork's Person p param and there's no easy way of turning your JSON object into a string. If you try "data.toString()" you'll just get an "[object Object]" value (remind you of anything?), which is not what we want. So here's a slightly modified function that will take your JSON and turn it into a string.

Note* The JSON de/serialization handles Date/Time in a specific way. The json2string function below does not take this into account. I'm sure there are some implementations out there which will work with ASP.NET AJAX but this one does not. For more information on this you can go here.

Update [4/11/08]: The javascript below has a few issues so it's been suggested that you should use the JSON.org version to "stringify" your object. You can download the script from here.

Update [4/25/08]: Rick Strahl has modified the JSON.org script so that it will properly create the dates to work with ASP.NET AJAX (read his post)

[code:js]
function json2string(strObject) {
var c, i, l, s = '', v, p;

switch (typeof strObject) {
case 'object':
if (strObject) {
if (strObject.length && typeof strObject.length == 'number') {
for (i = 0; i < v =" json2string(strObject[i]);" class="kwrd">if (s) {
s += ',';
}
s += v;
}
return '[' + s + ']';
} else if (typeof strObject.toString != 'undefined') {
for (i in strObject) {
v = strObject[i];
if (typeof v != 'undefined' && typeof v != 'function') {
v = json2string(v);
if (s) {
s += ',';
}
s += json2string(i) + ':' + v;
}
}
return '{' + s + '}';
}
}
return 'null';
case 'number':
return isFinite(strObject) ? String(strObject) : 'null';
case 'string':
l = strObject.length;
s = '"';
for (i = 0; i < c =" strObject.charAt(i);" class="kwrd">if (c >= ' ') {
if (c == '\\' || c == '"') {
s += '\\';
}
s += c;
} else {
switch (c) {
case '\b':
s += '\\b';
break;
case '\f':
s += '\\f';
break;
case '\n':
s += '\\n';
break;
case '\r':
s += '\\r';
break;
case '\t':
s += '\\t';
break;
default:
c = c.charCodeAt();
s += '\\u00' + Math.floor(c / 16).toString(16) +
(c % 16).toString(16);
}
}
}
return s + '"';
case 'boolean':
return String(strObject);
default:
return 'null';
}
}
[/code]

Now that we have a function to turn our JSON object into a string we need to go back and update the "mydata" variable that we defined above. After applying the json2string function we should have the following:

[code:js]
var mydata = { "Name":"Denny", "Age":23, "Shoes":["Nike","Osiris","Etnies"] };
var jsonStr = "p=" + json2string(mydata);
[/code]

Notice that I prepended the "p=" string to our json string. "p" matches the parameter name in our "DoWork()" method. So if our parameter name was "Dude" ( i.e. DoWork(Person Dude) ) then we would use "Dude=" instead.

Now that we've built the querystring to the web service we can see what our call is going to look like:

http://www.dennydotnet.com/Service.svc/DoWork/?p={ "Name":"Denny", "Age":23, "Shoes":["Nike","Osiris","Etnies"] }

You may get a URL Encoded value too, which would look like:

http://www.dennydotnet.com/Service.svc/DoWork/?p=%7b+%22Name%22%3a%22Denny%22%2c+%22Age%22%3a23%2c+%22Shoes%22%3a%5b%22Nike%22%2c%22Osiris%22%2c%22Etnies%22%5d+%7d%3b

Go ahead and link "jsonStr" to the "SendAjax()" javascript method so we can debug our service and verify that the data was passed through to the service... check it out:

And now you just need to implement your logic in the DoWork() method. Notice how you don't have to do any de/serialization on the WCF service side either, it's already done for you. Now you should certainly implement some exception management so that you don't get any invalid data, or even add some authentication, but I'll leave that up to you...

1/16/2008

以一卖烧饼的故事 来描述股市

  有一个故事,来看看能不能解答你的疑问。
  
  假设一个市场,有两个人在卖烧饼,有且只有两个人,姑且称他们为烧饼甲、烧饼乙。
  
  假设他们的烧饼价格没有物价局监管。
  
  假设他们每个烧饼卖一元钱就可以保本(包括他们的劳动力价值)
  
  假设他们的烧饼数量一样多。
  
  ——经济模型都这样,假设需要很多。
  
  再假设他们生意很不好,一个买烧饼的人都没有。这样他们很无聊地站了半天。
  
  甲说好无聊。
  
  乙说好无聊。
  
  看故事的你们说:好无聊。
  
  这个时候的市场叫做很不活跃!
  
  为了让大家不无聊,甲对乙说:要不我们玩个游戏?乙赞成。
  
  于是,故事开始了。。。。。。
  
  甲花一元钱买乙一个烧饼,乙也花一元钱买甲一个烧饼,现金交付。
  
  甲再花两元钱买乙一个烧饼,乙也花两元钱买甲一个烧饼,现金交付。
  
  甲再花三元钱买乙一个烧饼,乙也花三元钱买甲一个烧饼,现金交付。
  
  。。。。。。
  
  于是在整个市场的人看来(包括看故事的你)烧饼的价格飞涨,不一会儿就涨到了每个烧饼60元。但只要甲和乙手上的烧饼数一样,那么谁都没有赚钱,谁也没有亏钱,但是他们重估以后的资产“增值”了!甲乙拥有高出过去很多倍的“财富”,他们身价提高了很多,“市值”增加了很多。
  
  这个时候有路人丙,一个小时前路过的时候知道烧饼是一元一个,现在发现是60元一个,他很惊讶。
  
  一个小时以后,路人丙发现烧饼已经是100元一个,他更惊讶了。
  
  又一个小时以后,路人丙发现烧饼已经是120元一个了,他毫不犹豫地买了一个,因为他是个投资兼投机家,他确信烧饼价格还会涨,价格上还有上升空间,并且有人给出了超过200元的“目标价”(在股票市场,他叫股民,给出目标价的人叫研究员)。
  
  在烧饼甲、烧饼乙“赚钱”的示范效应下,甚至路人丙赚钱的示范效应下,接下来的买烧饼的路人越来越多,参与买卖的人也越来越多,烧饼价格节节攀升,所有的人都非常高兴,因为很奇怪:所有人都没有亏钱。。。。。。
  
  这个时候,你可以想见,甲和乙谁手上的烧饼少,即谁的资产少,谁就真正的赚钱了。参与购买的人,谁手上没烧饼了,谁就真正赚钱了!而且卖了的人都很后悔——因为烧饼价格还在飞快地涨。。。。。。
  
  那谁亏了钱呢?
  
  答案是:谁也没有亏钱,因为很多出高价购买烧饼的人手上持有大家公认的优质等值资产——烧饼!而烧饼显然比现金好!现金存银行能有多少一点利息啊?哪比得上价格飞涨的烧饼啊?甚至大家一致认为市场烧饼供不应求,可不可以买烧饼期货啊?于是出现了认购权证。。。。。。
  
  有人问了:买烧饼永远不会亏钱吗?看样子是的。但这个世界就那么奇怪,突然市场上来了一个叫李子的,李子曰:有亏钱的时候!那哪一天大家会亏钱呢?
  
  假设一:市场上来了个物价部门,他认为烧饼的定价应该是每个一元。(监管)
  
  假设二:市场出现了很多做烧饼的,而且价格就是每个一元。(同样题材)
  
  假设三:市场出现了很多可供玩这种游戏的商品。(发行)
  
  假设四:大家突然发现这不过是个烧饼!(价值发现)
  
  假设五:没有人再愿意玩互相买卖的游戏了!(真相大白)
  
  如果有一天,任何一个假设出现了,那么这一天,有烧饼的人就亏钱了!那谁赚了钱?就是最少占有资产——烧饼的人!
  
  这个卖烧饼的故事非常简单,人人都觉得高价买烧饼的人是傻瓜,但我们再回首看看我们所在的证券市场的人们吧。这个市场的有些所谓的资产重估、资产注入何尝不是这样?在ROE高企,资产有高溢价下的资产注入,和卖烧饼的原理其实一样,谁最少地占有资产,谁就是赚钱的人,谁就是获得高收益的人!
  
  所以作为一个投资人,要理性地看待资产重估和资产注入,忽悠别人不要忽悠自己,尤其不要忽悠自己的钱!
  
  在高ROE下的资产注入,尤其是券商借壳上市、增发购买大股东的资产、增发类的房地产等等资产注入,一定要把眼睛擦亮再擦亮,慎重再慎重!
  
  因为,你很可能成为一个持有高价烧饼的路人!

1/11/2008

伟大的中文——世间牛人到此哑口无言!

1、赵元任《施氏食狮史》      石室诗士施氏,嗜狮,誓食十狮。施氏时时适市视狮。十时,适十狮适市。是时,适施氏适市。氏视是十狮,恃矢势,使是十狮逝世。氏拾是十狮尸,适石室。石室湿,氏使侍拭石室。石室拭,氏始试食是十狮。食时,始识是十狮,实十石狮尸。试释是事。
2、杨富森<<于瑜与余欲渔遇雨>>
于瑜欲渔,遇余于寓。语余:“余欲渔于渝淤,与余渔渝欤?”余语于瑜:“余欲鬻玉,俞禹欲玉,余欲遇俞于俞寓。”  余与于瑜遇俞禹于俞寓,逾俞隅,欲鬻玉于俞,遇雨,雨逾俞宇。余语于瑜:“余欲渔于渝淤,遇雨俞寓,雨逾俞宇,欲渔欤?鬻玉欤?”  于瑜与余御雨于俞寓,俞鬻玉于余禹,雨愈,余与于瑜踽踽逾俞宇,渔于渝淤。
3、《季姬击鸡记》
  季姬寂,集鸡,鸡即棘鸡。棘鸡饥叽,季姬及箕稷济鸡。鸡既济,跻姬笈,季姬忌,急咭鸡,鸡急,继圾几,季姬急,即籍箕击鸡,箕疾击几伎,伎即齑,鸡叽集几基,季姬急极屐击鸡,鸡既殛,季姬激,即记《季姬击鸡记》。
4、《遗镒疑医》
  伊姨殪,遗亿镒。伊诣邑,意医姨疫,一医医伊姨。翌,亿镒遗,疑医,以议医。医以伊疑,缢,以移伊疑。伊倚椅以忆,忆以亿镒遗,以议伊医,亦缢。噫!亦异矣!  5、《易姨医胰》
  易姨悒悒,依议诣夷医。医疑胰疫,遗意易姨倚椅,以异仪移姨胰,弋异蚁一亿,胰液溢,蚁殪,胰以医。易胰怡怡,贻医一夷衣。医衣夷衣,怡怡奕奕。噫!以蚁医胰,异矣!以夷衣贻夷医亦宜矣!  6、 赵元任《熙戏犀》
  西溪犀,喜嬉戏。席熙夕夕携犀徙,席熙细细习洗犀。犀吸溪,戏袭熙。席熙嘻嘻希息戏。惜犀嘶嘶喜袭熙。  7、《饥鸡集矶记》
  唧唧鸡,鸡唧唧。几鸡挤挤集矶脊。机极疾,鸡饥极,鸡冀己技击及鲫。机既济蓟畿,鸡计疾机激几鲫。机疾极,鲫极悸,急急挤集矶级际。继即鲫迹极寂寂,继即几鸡既饥,即唧唧。  8、《侄治痔》
芝之稚侄郅,至智,知制纸,知织帜,芝痔炙痔,侄至芝址,知之知芷汁治痔,至芷址,执芷枝,蜘至,踯侄,执直枝掷之,蜘止,侄执芷枝至芝,芝执芷治痔,痔止。  9、 最后也是最变态的:
  《羿裔熠邑彝》  羿裔熠①,邑②彝,义医,艺诣。  熠姨遗一裔伊③,伊仪迤,衣旖,异奕矣。  熠意④伊矣,易衣以贻伊,伊遗衣,衣异衣以意异熠,熠抑矣。  伊驿邑,弋一翳⑤,弈毅⑥。毅仪奕,诣弈,衣异,意逸。毅诣伊,益伊,伊怡,已臆⑦毅矣,毅亦怡伊。  翌,伊亦弈毅。毅以蜴贻伊,伊亦贻衣以毅。  伊疫,呓毅,癔异矣,倚椅咿咿,毅亦咿咿。  毅诣熠,意以熠,议熠医伊,熠懿⑧毅,意役毅逸。毅以熠宜伊,翼逸。  熠驿邑以医伊,疑伊胰痍⑨,以蚁医伊,伊遗异,溢,伊咦。熠移伊,刈薏⑩以医,伊益矣。  伊忆毅,亦呓毅矣,熠意伊毅已逸,熠意役伊。伊异,噫,缢。  熠癔,亦缢。
  注解:  ①熠:医生,据说为后羿的后裔。  ②邑:以彝为邑,指居住在一个彝族聚居的地方。  ③伊:绝世佳丽,仪态万方,神采奕奕。  ④意:对伊有意思,指熠爱上了伊。  ⑤翳:有遮蔽的地方,指伊游弋到了一个阴凉的地方。  ⑥毅:逍遥不羁的浪人,善于下棋,神情坚毅,目光飘逸。  ⑦臆:主观的感觉,通“意”,指对毅有好感。  ⑧懿:原意为“懿旨”,此处引申为要挟,命令。  ⑨胰痍:胰脏出现了疮痍。  ⑩刈:割下草或者谷物一类。薏:薏米,白色,可供食用,也可入药

1/02/2008

delete content history in MSTSC

HKEY_CURRENT_USER\Software\Microsoft\Terminal Server Client\Default

12/08/2007

在Leopard里配置rails+php+apache+mysql

在Leopard里,很多开源代码都升级到了最新版,比如Python 2.5.1,PHP 5.2.4,Ruby 1.8.6,Apache 2.2,更棒的是,这次系统里直接集成了ruby on rails,包括Mongrel和Capistrano,据说还做过优化……
以前在mac里配置ROR,我是按照Dan Benjamin的《Building Ruby, Rails, Subversion, Mongrel, and MySQL on Mac OS X》来做的,安装ruby,gem之类的东西都是依靠MacPorts(osx里的包管理系统),而MacPorts安装的程序都在/opt/local里,独立于系统环境……
作为一个完美主义者,一定不能容忍自己心爱的系统里有任何兀余的代码,所以,这一次我希望能在leopard已经集成的代码基础上,搭建rails+php+apache+mysql的开发环境……
————————————————完美主义者的分割线——————————————————-
首先搞定apache,在配置面板里的“共享”中,选中”web共享”,就可以启动apache。默认的http://localhost指向/Library/WebServer/Documents,如果想修改成自己的目录,比如/Users/dexteryy/Sites/www,最简单的方法是直接改掉httpd.conf里的DocumentRoot
httpd.conf是放在/etc/apache2/里的,除了DocumentRoot,还要记得把下面两行代码前的注释删掉:
LoadModule php5_module libexec/apache2/libphp5.soLoadModule fastcgi_module libexec/apache2/mod_fastcgi.so
————————————————完美主义者的分割线——————————————————-
然后是php.ini,系统默认在/etc里放了一个php.ini.default,把它copy一份,改名为php.ini
php.ini里有一个关于mysql的地方要注意,leopard好像修改过mysql.sock的位置,所以要修改以下两行:
mysql.default_socket = /private/tmp/mysql.sock
mysqli.default_socket = /private/tmp/mysql.sock
————————————————完美主义者的分割线——————————————————-
然后安装最新的mysql,我下载的是mysql-5.0.45-osx10.4-i686.dmg。其中的MySQLStartupItem.pkg和mysql.prefpane都不用安装了,因为这两个东西目前都不起作用-___-b
以前启动mysql都是用这个命令:
sudo /Library/StartupItems/MySQLCOM/MySQLCOM start
但是在leopard里,这个东西好像失效了,只好用这个命令来启动
sudo /usr/local/mysql/bin/safe_mysqld
如果要自动启动mysql,可以用以下方法:
在/Library/LaunchDaemons/里新建一个叫com.mysql.mysqld.plist的文件:




KeepAlive

Label
com.mysql.mysqld
Program
/usr/local/mysql/bin/mysqld_safe
RunAtLoad



修改权限:sudo chown root /Library/LaunchDaemons/com.mysql.mysqld.plist
ok了……注意,如果安装过MySQLStartupItem.pkg,最好把MYSQLCOM直接删掉:
sudo rm -R /Library/StartupItems/MYSQLCOM
————————————————完美主义者的分割线——————————————————-
ruby on rails已经完美的集成了,不需要做任何设置,除了跟mysql的绑定……
sudo gem install mysql — –with-mysql-dir=/usr/local/mysql
选择3……
————————————————完美主义者的分割线——————————————————-
现在可以新建rails的项目了,在终端里进入刚才设置的DocumentRoot目录,输入/rails rails_test
然后启动服务器:rails_test/script/server
命令执行后不要关闭终端窗口(会关闭服务器),leopard的终端现在支持标签了,苹果键+T,继续做别的事情……
现在访问http://localhost:3000/ ,就可以看到rails项目的页面了……
这里启动的服务器是Mongrel,实际上可以把它跟apache整合到一起,用apache接受浏览器的请求,利用反向代理功能转发给Mongrel,这样就不需要用3000端口,直接用这样的地址就可以访问rails的页面:http://localhost/rails_test
方法很简单,找到刚才的httpd.conf,在最下面加入:
ProxyPass /rails_test http://localhost:3000
————————————————完美主义者的分割线——————————————————-
以上是我目前搭建的一个简单的rails+php+apache+mysql开发环境,可能还有其他问题,在具体使用中慢慢改……

12/01/2007

useing PHP to read MSSQL ntext fields

set php.ini
mssql.textlimit = 40960000
mssql.textsize = 40960000
restart IIS

Solution for the following Error:
Warning: mssql_query() [function.mssql-query]: message: Unicode data in a Unicode-only collation or ntext data cannot be sent to clients using DB-Library (such as ISQL) or ODBC version 3.7 or earlier.
This is because you are using column types of like ntext instead of text. There are 2 solutions.
1. Change all ntext column types to text or
2. Your query must look like: SELECT CAST(field1 AS TEXT) AS field1 FROM table

Another Error:
Allowed memory size of 134217728 bytes exhausted

@ini_set('memory_limit','1024M');
all done!

11/29/2007

Adobe CS 3中文版破解激活方法总结

破解方法一:1、安装 design standard.
2、完成安装后,输入注册码:1327-0612-1519-8513-7027-3574(坛子里借鉴的)
3、由于是升级版的sn,选择cs2 premium,输入cs2 premium sn: 1131-0411-1224-3073-8242-8519再继续,会在线激活一次,提示激活用户太多。再选择其他激活方式。(可能有其他提示)
4、再选择电话激活。
5、在提示要激活码时,借助pc缉获白金版的缉获程序(在网上随便找一个白金版的激活程序),输入Activation Number,算出Authorization Code.把Authorization Code添入,把最后的5471改为5407(见图)。(改的原因是5471是白金版的末位码,5407我个人认为应该是标准版的配套末位码,这也是任何破解cs3版本区别与破解cs2的一个地方。)所有design standard全部激活。
要用扩展版的photoshop的话,就把应用程序下的photoshop做个迁移,换到其他文件夹。再安装扩展版(最好是那个免激活的版本,省事),安装完毕后,只需把英文扩展版的Adobe Photoshop CS3/Adobe Photoshop CS3/Contents/Resources/AMT复制一份到中文版标准版的同一位置替换AMT就可以了(Adobe Photoshop CS3/Adobe Photoshop CS3/Contents/Resources/)。两个版本,想用那个用那个
破解方法二:1. 安装 Adobe Design Standard 简体中文标准版--清除旧软件后,等同于全新干净安装。
2. 完成安装后,随便打开其中一个程序,打开 Photoshop CS3,出现序列号输入信息,输入“1327-0612-1519-8513-7027-3574”,绿勾提示通过后,继续下一步。
3. 因为之前已清除掉全部老软件信息,这一步会提示说以上序列号为升级用途--不晓得,也许上面序列号就是升级版本用的,需要输入旧的 CS2 简体中文版序列号。我输入的就是原 4CD 中文白金版 CS2 用的“1131-0419-6657-1041-6672-5946”,并在下拉菜单上选择对应的“Adobe CS2 Design Premium” 。这选择一定要正确,如果选择“Adobe CS2 Standard”的话,该序列号是无效的。
4. 通过以上验证后,接下来就是激活过程。选择“其他激活方式/电话激活”(非“在线激活”),获得激活号码后,将其输入到“Adobe CS3 Design Premium”注册机里,算出验证号码,会是“xxxx-...-5471”的形式。无需再作任何更改,直接正确输入后,激活就会成功。
破解方法三:推荐 Hackintosh 或 uphuck 系统(装了 AppleSMBIOS 文件)用户使用,PPC 系统未测试!准备工作:首先要有两份 CS3 安装文件,包括 Design Standard 简体中文标准版和 Design Premium 英文白金版。
一、安装 Design Standard 简体中文标准版,将“应用程序”内的所有 Adobe 软件备份 议使用 dmg 压缩,以后要用时第一步可省略,永久受用),再使用安装程序将其卸载, 重启电脑(此过程不用激活、不需运行程序,直接备份完后卸载);
二、安装 Design Premium 英文白金版,输入下列序列号免激活(符合上述提及之系统需求):
  1326-0651-7402-0424-4066-8309
  英文版激活完成后,暂时移至别处,如:建立一个名为“EN”的文件夹放置其中;
三、将备份的简体中文版的所有程序拖放到“应用程序”文件夹里,然后再使用之前移动的英  文版中 Illustrator、InDesign、Photoshop/Contents/Resources/AMT/ 文件夹下的文件  替换简体中文版的对应文件即可,其它软件无需替换;
四、如果之前运行过英文版而导致中文版不正常的(如 AI CS3),请在运行中文软件的  “同时”按住“Cmd+Alt+Shift”删除首选项(预置)文件即可;
五、Photoshop CS3 由标准版变为增强版了!
六、补充几点注意事项:  1、首先确定英文白金版在您的当前系统可以激活(非暴力破解);  2、在 2 楼下载的 AMT 文件不能直接用在简体中文版上,遵循“中文版装完备份后卸载-再装英文版激活-替换文件操作”顺序进行;  3、没有英文白金版的勿试;  4、PPC 机台未测试,慎用!
破解方法四: 如果以上方法都不会用,这种方法最简单.到这里下载第四个地址,即Adobe CS3破解补丁下载地址4,直接按里面的说明替换文件即可破解.http://www.chinamac.com/macsoft/html/66/3933.html

11/06/2007

Finder, order directory before files

One of most popular problems wose that swicher but also people that work in many directory is that File and Directory are mixed, and is strange because under linux and windows if I order for Type/Kind, folder have a priority, and are displayed before files!

Under Tiger/Leopard, there isnt' a good way for do that, but why? I've searched on google for few days without any result, other than the some tips like order by size or use finder replacement like path finder.

So, I decided to do all thing for make finder better, and the solution that I found is very very simple to do but very very usefull.

The first thing that I do wose, set details view and order by Kind

Now, you can see (on image), that Folders are displayed after "Archivie Zip".

So I searched in all my system files, the word "Folder" for rename it in "~Folder"

So go here:

/System/Library/CoreServices/Finder.app/Contents/Resources/English.lproj/
and open with your preferred edito (Mine is TextMate) and open this file:
InfoPlist.string

You see something like that:

Add "~" (Alt+5) before "Folder", like the image and save the file.

Your editor ask for root password.

After save, restart your Finder, right click on Finder icon under pressing the key ALT and Reopen your Finder.



Reopen your directory and order it by Kind and you su this very very very beautifull result!


Very good no???

10/21/2007

Macintosh10.4.8 apache2 php5 mediawiki1.8

#install mysql
cd ~/mysql-5.027
./configure --prefix=/usr/local/mysql \
--with-unix-socket-path=/usr/local/run/mysql_socket \
--with-mysqld-user=mysql \
--with-comment \
--with-debug
make
sudo make install
sudo make clean
sudo /usr/local/mysql/bin/mysql_install_db --force
sudo mkdir /usr/local/mysql/run
sudo chgrp -R mysql /usr/lcoal/mysql
sudo chown -R mysql /usr/local/mysql/run /usr/local/mysql/var
sudo /usr/local/mysql/bin/mysqld_safe --user=mysql &
/usr/local/mysql/bin/mysqladmin -u root password picb
sudo mkdir /var/mysql
cd /var/mysql
sudo ln -s /usr/local/run/mysql_socket ./mysql.sock

#istall apache2
cd ~/httpd-2.2.3
./configure --prefix=/usr/local/apache2 \
--enable-module=most \
--enable-shared=max
make
sudo make install
sudo make clean
sudo /usr/local/apache2/bin/apachectl start

#install GD (before this we should have installed or have to install libxml2,zlib,libpngjpeg,
#freetype please check it, my OS no need to install libxml2 & zlib, but can install as follow: )

#install libxml2
cd ~/libxml2-2.6.27
./configure
make
sudo make install
sudo makeclean

#install zlib
cd ~/zlib-1.2.3
./configure
make
sudo make install
sudo makeclean

#install libpng
cd ~libpng-1.2.14
cp scripts/makefile.darwin Makefile
vi makefile
# Where the zlib library and include files are located
ZLIBLIB=/usr/local/lib
ZLIBINC=/usr/local/include
#ZLIBLIB=../zlib
#ZLIBINC=../zlib
make
sudo make install
sudo make clean

#install jpeg
cd ~/jpeg-6b/
ln -s /usr/bin/glibtool ./libtool
export MACOSX_DEPLOYMENT_TARGET=10.4
cp /usr/share/libtool/config.sub .
cp /usr/share/libtool/config.guess .
./configure --prefix=/usr/local/jpeg6 \
--enable-shared
make
sudo mkdir /usr/localjpeg6
sudo mkdir /usr/localjpeg6/include
sudo mkdir /usr/localjpeg6/lib
sudo mkdir /usr/localjpeg6/bin
sudo mkdir /usr/localjpeg6/man
sudo mkdir /usr/localjpeg6/man/man1
sudo make install-lib
sudo make install
sudo make clean

#install freetype2
cd ../freetype-2.2.1
/*edit the file include/freetype/config/ftoption.h and uncomment line 439 to read:
#define TT_CONFIG_OPTION_BYTECODE_INTERPRETER
*/
./configure --prefix=/usr/local/freetype2
make
sudo make install
sudo make clean

#install GD
sudo ln -s /usr/X11R6/include/fontconfig /usr/local/include
cd ~/gd-2.0.33
./configure --prefix=/usr/local/gd2 \
--with-zlib \
--with-png=/usr/local/libpng2 \
--with-jpeg=/usr/local/jpeg6 \
--with-freetype=/usr/local/freetype2
make
sudo make install
sudo make clean

#install libiconv
cd ~/libiconv-1.11
./configure --prefix=/usr/local/libiconv
make
sudo make install
sudo make clean

#install php5
cd ~/php-5.2.0
cd ./ext/iconv
ln -s /usr/local/libiconv/include/iconv.h iconv.h
./configure --prefix=/usr/local/php \
--with-zlib --with-xml \
--with-ldap=/usr \
--with-mysql=/usr/local/mysql \
--with-gd \
--with-jpeg-dir=/usr/local/jpeg6 \
--with-png-dir=/usr/local/libpng2 \
--with-iconv=/usr/local/libiconv \
--with-apxs2=/usr/local/apache2/bin/apxs
--enable-cli --enable-exif \
--enable-ftp --enable-mbstring \
--enable-dbx --enable-sockets \
--with-iodbc=/usr --with-curl=/usr
make
sudo make install
sudo make clean
sudo cp php.ini-dist /usr/lcoal/php/lib/php.ini
vi apache2/conf/httpd.conf
/* add these lines

AddType application/x-httpd-php .php
AddType application/x-httpd-php-source .phps
DirectoryIndex index.html index.php

LoadModule php5_module modules/libphp5.so (if not work add it)
LoadModule mod_php5 /usr/local/php/libphp5.so (if not work add it)
*/


#setup mediawiki
WiKi name picb
contactmail andy@picb.ac.cn
language en-English
license no liense metadata
Adminname WikiSysop
password picb
share M no cache
Database Type MySOL
Database host localhost
DB name wikidb
DB usrname wikiuser
password picb
super acco root
password picb

mv ~/config/LocalSettings.php ~/
rm -rf ~/config

#wiki Localsetting.php configure
#use EmEditor to edit this file and save in type of UTF-8 and erase tickle of "Add a Unicode Signature(BOM)"
change $wgEnableUploads =true; #allow user to upload file

add $wgLocalTZoffset = 8; #set time zone

require_once( 'LdapAuthentication.php' ); #set ldap
$wgAuth = new LdapAuthenticationPlugin();
$wgLDAPDomainNames = array( "icb.ac.cn" );
$wgLDAPServerNames = array( "icb.ac.cn"=>"10.10.118.2");
$wgLDAPSearchStrings = array( "icb.ac.cn"=>"uid=USER-NAME,cn=users,dc=pdc,dc=icb,dc=ac,dc=cn");
$wgLDAPUseSSL = false;
$wgLDAPUseLocal = true;
$wgLDAPAddLDAPUsers = false;
$wgLDAPUpdateLDAP = false;
$wgLDAPMailPassword = false;
$wgLDAPRetrievePrefs = false;
$wgMinimalPasswordLength = 1;

#authenticat upload file type
$wgShowIPinHeader = false; #restrict user right to create account
$wgGroupPermissions['*' ]['createaccount'] = false;
$wgGroupPermissions['*' ]['edit'] = false;

$wgWhitelistRead = array( "Main Page", "Special:Userlogin" ); #anonymousr only can reae main page
$wgGroupPermissions['*' ]['read'] = false;

$wgFileExtensions = array( 'png', 'gif', 'jpg', 'jpeg', 'pdf', 'ppt', 'zip', 'rar', 'doc', 'avi', 'mp3', 'rm', 'txt', 'rmvb', 'mpg', 'csv', 'xls' );
/** Files with these extensions will never be allowed as uploads. */
$wgFileBlacklist = array(
# HTML may contain cookie-stealing JavaScript and web bugs
'html', 'htm', 'js', 'jsb',
# PHP scripts may execute arbitrary code on the server
'php', 'phtml', 'php3', 'php4', 'phps',
# Other types that may be interpreted by some servers
'shtml', 'jhtml', 'pl', 'py', 'cgi',
# May contain harmful executables for Windows victims
'exe', 'scr', 'dll', 'msi', 'vbs', 'bat', 'com', 'pif', 'cmd', 'vxd', 'cpl' );

cp LdapAuthentication.php to ~/wiki/includes
chomod a+w+r ~/wiki/images
#to support show formula through latex
download and install ocaml & mactex
cd ~/mediawiki/math
make
and then we can see the texvc be install under directory math, however the apache use it so we should add it to PATH
export PATH=$PATH;~/mediawiki/math
enable $wgUseTeX in LocalSettings.php