Search Unity

  1. If you have experience with import & exporting custom (.unitypackage) packages, please help complete a survey (open until May 15, 2024).
    Dismiss Notice
  2. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice

RESTfulWWW: Web Service Kit (in game chat/highscore leaderboard/ and more!)

Discussion in 'Assets and Asset Store' started by Haptix Games, Aug 9, 2012.

  1. Haptix Games

    Haptix Games

    Joined:
    Aug 8, 2012
    Posts:
    29
    $logo.png

    For quicker support please visit our forum at http://www.haptixgames.com/forum.

    [>>>CHECK OUT THE EXAMPLE ON OUR SITE<<<]

    BUY IT HERE!


    What is RESTfulWWW: Web Service Kit?


    RESTfulWWW is an implementation of HTTP GET and POST actions in a friendly wrapper class for Unity3D. The asset includes server and client source code, giving you an end-to-end solution, ready to run in minutes.

    The included sample server is developed using Microsoft WCF technology and exposed as a HTTP endpoint. The implementation includes custom message inspector behaviour giving you access to incoming and outgoing HTTP headers. For more information on WCF and REST please visit the pages listed below.

    http://www.robbagby.com/rest/rest-in-wcf-part-i-rest-overview/
    http://blogs.msdn.com/b/carlosfigueira/archive/2011/03/14/wcf-extensibility.aspx



    What is RESTfulWWW good for?

    We use RESTfulWWW in our game for our HIGHSCORE leader board system as well our in game chat function and feature update notifications. We wanted to have full control of what was sent to and from clients in an easy to implement way.

    After adding the WebServiceProxy component to your GameObject, in a few simple lines of code you can have the ability to POST and GET high scores from your server, which can either be localhost or a remote server. All server functionality shown in the demo is included.


    Example:
    proxyPostScore.POST (“http://domain.com/my.svc/PostScore", jsonString, customHeaders, timeoutInSeconds);


    $restfulWWW_leaderboard.png


    Also included in the package is the in game chat.


    $restfulWWW_chat.png


    Does it work in Unity Free / iPad / iPhone / Android?
    Yes, this is a server based solution so you aren’t limited to platform. The client code is all WWW based and does not use the System.Net namespace. For instance, if you let iOS Game Center handle your high scores, you will have to write a separate solution for Android and other platforms.





    Features
    • Easy HTTP wrapper class to let you use HTTP GET and POST functions with a few lines of code.
    • Examples include: Posting and Getting high scores from server, as well as in game chat amongst other things.



    RESTfulWWW IS OUT!




    www.haptixgames.com


    tags: highscore, high score, leaderboard, leader board, in game chat, www, http, post, get, server
     
    Last edited: Feb 18, 2013
  2. Jum

    Jum

    Joined:
    Aug 20, 2011
    Posts:
    63
    Does it support flash build?
     
  3. Chris-HG

    Chris-HG

    Joined:
    Aug 10, 2012
    Posts:
    63
    Jum, I will be attempting a core build in Flash by next week.
    Thanks for the excellent question.

    -Chris
     
  4. dreamlarp

    dreamlarp

    Joined:
    Apr 22, 2011
    Posts:
    854
    What do you think the price will be around? Are you going to keep adding features. like forums ect to this?
     
  5. Chris-HG

    Chris-HG

    Joined:
    Aug 10, 2012
    Posts:
    63
    Hi dreamlarp.

    We have not set a cost for the core send/receive functionality and all client and server source. Please feel free to suggest a price through the demo in the first link. We will track your price suggestions and adjust it fit everyone's budget.

    We were afraid that this question would come up. The demo does include examples of simple chat and leaderboard implementations. The goal of this asset was to provide the basic send/receive functionality and leave the detailed implementation up to you. We also understand that game developers do not have the time to build a feature rich REST web service that interfaces with other services or databases. The asset does include all service source to provide you with the building blocks. If there is enough interest then maybe we could spawn off another, specialized asset (forum / chat / mutliplatform saves).

    What would be other features you would like to see besides forums?

    -Chris
     
  6. Chris-HG

    Chris-HG

    Joined:
    Aug 10, 2012
    Posts:
    63
    FLASH SUPPORTED!

    Jum, Yes the bare RESTfulWWW functionality is supported in Flash.:cool:


    $Capture.JPG
     
  7. Chris-HG

    Chris-HG

    Joined:
    Aug 10, 2012
    Posts:
    63
    For those that like to look at API innards before you buy I am including some tutorial documentation which highlights the functionality.


    The below sample code uses a simple JSON parser I quickly found on the web. The parser is included on both client and server sides so that we’re able to parse the data being passed around. Here is how to use the library to send and receive chat messages.

    The Server implementation includes two operations: PostChatMessage(messageStream) and GetChatMessages(clientInformation). PostChatMessage posts your message while at the same time returning any un-received messages (might as well kill 2 birds with one stone). During the lifetime of the client, GetChatMessages is called repeatedly to receive any new messages. The rate at which GetChatMessages is called is adjusted by the client based on your network latency.

    In WCF hosting you have several options. You can allow IIS to host your service or you can host it inside of a console application or Windows Service. The choice is up to you. With the service up and running, we are ready to interact with it.


    In your client, we must first create a web proxy. This is done by adding a WebServiceProxy component to your game object.

    Code (csharp):
    1.      private WebServiceProxy CreateProxy()
    2.      {    
    3.            return gameObject.AddComponent<WebServiceProxy>();
    4.      }
    With the above created proxy we need to subscribe to the proxy OnCompleted event. Inside the OnCompleted event handler you will be able to check the proxy response state and access returned errors and data.

    Code (csharp):
    1.      WebServiceProxy proxyChatSend = CreateProxy();
    2.      proxyChatSend.OnCompleted += HandleProxyChatSendOnCompleted;
    We then build the message, encode it into a JSON string and send it.

    Code (csharp):
    1.      Hashtable msg = new Hashtable();
    2.      msg.Add ("nick", “my_nick”);
    3.      msg.Add ("message", “hello world”);
    4.                                      
    5.      proxyChatSend.POST(“http://domain.com/svc/PostChatMessage", JSON.JsonEncode(msg)));
    The WWW wrapper will take over and the operation will either complete successfully, complete with errors or timeout. In all three scenarios our HandleProxyChatSendOnCompleted handler will be invoked so that we can determine the next course of action. Remembering that PostChatMessage operation returns any new chat messages we can now look at our event handler. During the execution, the service operation performs a database query and encodes returned results into a JSON string.

    Code (csharp):
    1.      void HandleProxyChatSendOnCompleted (object sender, WebServiceProxyResponseArgs e)
    2.      {
    3.            //check proxy state and parse incoming data
    4.      }
    Inside the handler we can check our e parameter to a list of possible proxy states: COMPLETED, TIMEOUT, ERROR, CANCELLED, DISCONNECTED. The value of e.Response is the returned Unity WWW object where you can access errors and returned data.

    To decode our inbound data we perform JSON.JsonDecode on the e.Response.text value and iterate over the Hashtable and ArrayLists to post the new message content into our chat window.

    Code (csharp):
    1.      Hashtable response = (Hashtable)JSON.JsonDecode(text);
    2.                
    3.      foreach(Hashtable msg in response[“Message”] as ArrayList)
    4.      {
    5.           writeChatWindow( msg["Nick"], msg["Msg"]);
    6.      }
    Once you are done with the proxy component, you can call ((WebServiceProxy)sender).Dispose() to remove the component from the scene.

    Also at any time during a long running service request you can call Reset() on your proxy to cancel it. Your event handler will be invoked with a CANCELLED proxy state.
     
  8. Haptix Games

    Haptix Games

    Joined:
    Aug 8, 2012
    Posts:
    29
    Don't let that post above scare you, it really is an easy product.

    For instance, if you want to just post a high score to your server all you need to do is encode your name and score as a json string and post it to the server.

    Code (csharp):
    1.  
    2. //encode your name / score into a json string
    3. Hashtable datahash = new Hashtable();
    4. datahash.Add ("initials", _initials);
    5. datahash.Add ("score", _highScore);
    6. string jsonString = JSON.JsonEncode(datahash);
    7.  
    And then send it off to the internet:

    Code (csharp):
    1.  
    2. proxyPostScore.POST (url + "PostScore", jsonString, null, _advancedTimeout, false);
    3.  
    It is that easy...
     
  9. Haptix Games

    Haptix Games

    Joined:
    Aug 8, 2012
    Posts:
    29
    Just bumping to let everyone know, that this asset is now live on the asset store!
     
  10. Jum

    Jum

    Joined:
    Aug 20, 2011
    Posts:
    63
    thats it! I am gonna buy right now! Thanks
     
  11. Haptix Games

    Haptix Games

    Joined:
    Aug 8, 2012
    Posts:
    29
    With a decent number of sales, I'd like to invite users to post their questions here as well so the answers can help everyone out.


    [Grab a copy here!]
     
  12. ScaryRobotGames

    ScaryRobotGames

    Joined:
    May 2, 2007
    Posts:
    57
    Is your plugin compatible with javascript?
     
  13. Chris-HG

    Chris-HG

    Joined:
    Aug 10, 2012
    Posts:
    63
    Dustin,

    Do you mean is it compatible with in-Unity JS ? Or compatible with JS on the server side?

    If it is the first then the WWW wrapper is fairly small so converting to JS wouldn't be a big deal.
    If you mean REST using Node.js, then yes it is compatible.
     
  14. ScaryRobotGames

    ScaryRobotGames

    Joined:
    May 2, 2007
    Posts:
    57
    I was referring to the Unity side. But I now understand that there's more involved on the server side. I need to do more homework on a Unity/SQL setup.
     
    Last edited: Dec 14, 2012
  15. Chris-HG

    Chris-HG

    Joined:
    Aug 10, 2012
    Posts:
    63
    Hey Dustin, I PMed you an untested JS version. It will take some work on my side to test it throughly. My next question was going to be about the server side then. What kind of web-service are you trying to post or get data from? What kind of data will it be?
     
  16. ScaryRobotGames

    ScaryRobotGames

    Joined:
    May 2, 2007
    Posts:
    57
    Hey Chris, it'll be a database (mySQL) of user generated levels for my game that users will be able to upload to/download from as well as perform queries. I've been doing some testing with a Unity/PHP/mySQL approach. I was looking for a simplified approach that didn't require a ton of PHP script development on the server side but I'm learning that that's going to be the case no matter which approach I take.
     
  17. Chris-HG

    Chris-HG

    Joined:
    Aug 10, 2012
    Posts:
    63
    Do you have the level "schema" down? Pm me if you want to bounce some ideas.
     
  18. Slem

    Slem

    Joined:
    Jan 28, 2009
    Posts:
    191
    Can you do PUT and DELETE with this plugin?
     
  19. Chris-HG

    Chris-HG

    Joined:
    Aug 10, 2012
    Posts:
    63
    No, not natively. What is the service you want to hit?
     
  20. sonicviz

    sonicviz

    Joined:
    May 19, 2009
    Posts:
    1,051
  21. Chris-HG

    Chris-HG

    Joined:
    Aug 10, 2012
    Posts:
    63
    System.net is supported on ios, you might have to change to .net 2 subset.
     
  22. sonicviz

    sonicviz

    Joined:
    May 19, 2009
    Posts:
    1,051
  23. ParkerMan

    ParkerMan

    Joined:
    Mar 26, 2013
    Posts:
    11
    Is there anyway to get a copy of the sql database you use for the test.

    Thanks for the help. I just created a SQL script that creates a database, tables and user.
     
    Last edited: Mar 28, 2013
  24. Lostlogic

    Lostlogic

    Joined:
    Sep 6, 2009
    Posts:
    693
    So how is this different from using Unity's built in WWW support? Not trolling, really asking....
     
  25. Wcf

    Wcf

    Joined:
    Aug 15, 2013
    Posts:
    1

    <add baseAddress="http://juniper90.sync-io.net:8000/Service.svc" />
    Modify
    <add baseAddress="http://localhost:8000/Service.svc"/>

    $제목 없음.png

    Web address should not change. The solution?
     
  26. Chris-HG

    Chris-HG

    Joined:
    Aug 10, 2012
    Posts:
    63
    Man, I just got 3 notifications for posts from March 2013. That sucks, sorry for missing the posts.

    Wcf, let me see your web.config for the service.
     
  27. Chris-HG

    Chris-HG

    Joined:
    Aug 10, 2012
    Posts:
    63
    Lostlogic, the kit is a wrapper around WWW, which just makes it simpler to use since the kit has functionality built in to handle client timeouts (eg. you can specify the client to time out after 15 seconds).

    Here is how we are using the kit to do in-game code redemption:

    Code (csharp):
    1.  
    2.            WebOp_Redeem redeemCode = new WebOp_Redeem(Owner);
    3.         redeemCode.OnCompleted += (sender, e) =>
    4.             {
    5.                 WebOperationResponseArgs a = (WebOperationResponseArgs)e;
    6.                 if(! a.Response.IsServiceCallSuccessful)
    7.                 {
    8.                     inputText.text = "Failed to connect.";
    9.                 }
    10.                 else if (! a.Response.IsOperationCallSuccessful)
    11.                 {
    12.                     inputText.text = a.Response.OperationDetail;
    13.                 }
    14.                 else
    15.                 {
    16.                     inputText.text = "Thanks!";
    17.                 }
    18.            
    19.                 Utilities.GameCodes.ExecuteCommand(e.Response);
    20.            
    21.                 Debug.LogWarning( ((WebOperationResponseArgs)e).Response.ProxyState.ToString() );
    22.                 Debug.LogWarning( ((WebOperationResponseArgs)e).Response.ServiceStatusEnum);
    23.                 Debug.LogWarning( ((WebOperationResponseArgs)e).Response.OperationStatusEnum + ":" + ((WebOperationResponseArgs)e).Response.OperationDetail );
    24.             };
    25.        
    26.         redeemCode.Default().Send( inputText.text );
    27.  
    Code (csharp):
    1.  
    2. public class WebOp_Redeem: WebOperation
    3. {
    4.     public WebOp_Redeem(MonoBehaviourBase script): base(script){}
    5.    
    6.     [JsonMember(Name = "id")]
    7.     public string UUID { get; set; }  
    8.  
    9.     [JsonMember(Name = "c")]
    10.     public string Code { get; set; }
    11.    
    12.     public override WebOperation Default ()
    13.     {
    14.         UUID = SystemInfo.deviceUniqueIdentifier;
    15.         Code = "";
    16.         return this;
    17.     }
    18.    
    19.     protected override string EndpointOperationPost
    20.     {
    21.         get
    22.         {
    23.             return WebServiceValues.WEB_SVC_URL + WebServiceValues.WEB_SVC_OP_Redeem;
    24.         }
    25.     }
    26.    
    27.     public override EnumWebServiceProxyState Send(params string[] parameters)
    28.     {
    29.         Code = parameters[0];
    30.         string j = this.ToJson();
    31.        
    32.         return Proxy.POST(EndpointOperationPost, j, null, WebServiceValues.WEB_SVC_TIMEOUT, false);
    33.     }
    34. }
    35.  
    Code (csharp):
    1.  
    2. public abstract class WebOperation: WebConvertible
    3. {
    4.     protected MonoBehaviourBase Script;
    5.     protected WebServiceProxy Proxy;
    6.    
    7.     public event WebOperationResponse OnCompleted;
    8.    
    9.     public WebOperation(MonoBehaviourBase script)
    10.     {
    11.         if (script == null)
    12.             throw new Exception("Script for WebOperation creation can not be null.");
    13.        
    14.         Script = script;
    15.         Proxy =  Script.CreateWebProxy();
    16.         Proxy.OnCompleted += HandleProxyOnCompleted;
    17.     }
    18.  
    19.     void HandleProxyOnCompleted (object sender, WebServiceProxyResponseArgs e)
    20.     {
    21.         RestResponse response = (RestResponse) new RestResponse().Default();
    22.         response.ProxyState = e.State;
    23.        
    24.         if(e.State == EnumWebServiceProxyState.COMPLETED)
    25.             response = (RestResponse)response.FromJson(e.Response.text);
    26.        
    27.         if(OnCompleted  != null)
    28.         {
    29.             OnCompleted(this, new WebOperationResponseArgs(response));
    30.             Proxy.OnCompleted -= HandleProxyOnCompleted;
    31.         }
    32.        
    33.         Proxy.Dispose();
    34.     }
    35.    
    36.     protected abstract string EndpointOperationPost {get;}
    37.    
    38.     public virtual EnumWebServiceProxyState Send(params string[] parameters)
    39.     {
    40.         string j = this.Default().ToJson();
    41.         return Proxy.POST(EndpointOperationPost, j, null, WebServiceValues.WEB_SVC_TIMEOUT, false);
    42.     }
    43.    
    44.     public new virtual WebOperation Default() { return this; }
    45. }
    46.  
    Code (csharp):
    1.  
    2. ublic abstract class WebConvertible
    3. {
    4.     public WebConvertible FromJson(string json)
    5.     {
    6.        
    7.         Hashtable datahash;
    8.        
    9.         try
    10.         {  
    11.             PropertyInfo[] properties = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
    12.            
    13.            
    14.             datahash = (Hashtable)JSON.JsonDecode(json);
    15.        
    16.             foreach(PropertyInfo property in properties)
    17.             {
    18.                 JsonMemberAttribute[] atts = (JsonMemberAttribute[])property.GetCustomAttributes(typeof(JsonMemberAttribute), true);
    19.                 if (atts.Length == 1)
    20.                 {
    21.                     foreach(string k in datahash.Keys)
    22.                     {
    23.                         if(k == atts[0].Name)
    24.                         {
    25.                             property.SetValue(this, datahash[k], null);
    26.                             break;
    27.                         }
    28.                     }
    29.                 }
    30.             }
    31.            
    32.             return this;
    33.         }
    34.         catch (Exception ex)
    35.         {
    36.             return null;
    37.         }
    38.     }
    39.    
    40.     public string ToJson()
    41.     {
    42.        
    43.         Hashtable datahash = new Hashtable();
    44.         PropertyInfo[] properties = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
    45.         foreach(PropertyInfo property in properties)
    46.         {
    47.             JsonMemberAttribute[] atts = (JsonMemberAttribute[])property.GetCustomAttributes(typeof(JsonMemberAttribute), true);
    48.             if (atts.Length == 1)
    49.             {
    50.                 object value = property.GetValue(this, null);
    51.                 datahash.Add (atts[0].Name, (value == null ? "" : value.ToString()));  
    52.             }
    53.         }
    54.        
    55.         return JSON.JsonEncode(datahash);
    56.     }
    57.    
    58.     public virtual WebConvertible Default()
    59.     {
    60.         return null;
    61.     }
    62. }
    63.  
    Code (csharp):
    1.  
    2. public enum EnumRestResponseStatus
    3. {
    4.     UNKNOWN = -1,
    5.     FAILURE = 0,
    6.     SUCCESS = 1
    7. }
    8.  
    9. public class RestResponse: WebConvertible
    10. {
    11.     [JsonMember(Name = "ss")]
    12.     public string ServiceStatus { get; set; }
    13.    
    14.     public EnumRestResponseStatus ServiceStatusEnum
    15.     {
    16.         get
    17.         {
    18.             return (EnumRestResponseStatus)System.Enum.Parse(typeof(EnumRestResponseStatus), ServiceStatus);   
    19.         }
    20.     }
    21.    
    22.     [JsonMember(Name = "sd")]
    23.     public string ServiceDetail { get; set; }
    24.    
    25.     [JsonMember(Name = "os")]
    26.     public string OperationStatus { get; set; }
    27.    
    28.     public EnumRestResponseStatus OperationStatusEnum
    29.     {
    30.         get
    31.         {
    32.             return (EnumRestResponseStatus)System.Enum.Parse(typeof(EnumRestResponseStatus), OperationStatus); 
    33.         }
    34.     }
    35.    
    36.     [JsonMember(Name = "od")]
    37.     public string OperationDetail { get; set; }
    38.    
    39.     public EnumWebServiceProxyState ProxyState = EnumWebServiceProxyState.NONE;
    40.    
    41.     public bool IsServiceCallSuccessful
    42.     {
    43.         get
    44.         {
    45.             return ProxyState == EnumWebServiceProxyState.COMPLETED
    46.                                      ServiceStatusEnum == EnumRestResponseStatus.SUCCESS;
    47.         }
    48.     }
    49.    
    50.     public bool IsOperationCallSuccessful
    51.     {
    52.         get
    53.         {
    54.             return OperationStatusEnum == EnumRestResponseStatus.SUCCESS;
    55.         }
    56.     }
    57.    
    58.     public override WebConvertible Default ()
    59.     {
    60.         base.Default();
    61.        
    62.         ServiceStatus = "-1";
    63.         OperationStatus = "-1";
    64.         return this;
    65.     }
    66. }
    67.  
    Code (csharp):
    1.  
    2. [ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Single, InstanceContextMode=InstanceContextMode.PerCall)]
    3.     public class SvcImpl : Contracts.Ih2flowSvc
    4.    
    5. public RestResponse Redeem(Stream data)
    6.         {
    7.             h2Db db = null;
    8.             RestResponse response = null;
    9.  
    10.             try
    11.             {
    12.                 GameCode gc = Streams.Deserialize<GameCode>(data);
    13.                 db = new h2Db();
    14.                 //db.IdentifyWithStream(data);
    15.                 string detail = "";
    16.                 if (db.Redeem(gc, out detail))
    17.                     response = RestResponse.ServiceSuccess("1", detail);
    18.                 else
    19.                     response = RestResponse.ServiceSuccess("0", detail);
    20.             }
    21.             catch (Exception ex)
    22.             {
    23.                 lib.Helpers.Logger.LogError(ex);
    24.                 response = RestResponse.ServiceFailure(ex.Message);
    25.             }
    26.             finally
    27.             {
    28.                 try { db.Dispose(); }
    29.                 catch { }
    30.             }
    31.  
    32.             return response;
    33.         }
    34.  
    Code (csharp):
    1.  
    2.  [WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare)]
    3.         RestResponse Redeem(Stream data);
    4.  
    Code (csharp):
    1.  
    2. [DataContract]
    3.     public class RestResponse
    4.     {
    5.         [DataMember(Name = "ss")]
    6.         public string ServiceStatus { get; set; }
    7.  
    8.         [DataMember(Name = "sd")]
    9.         public string ServiceDetail { get; set; }
    10.  
    11.         [DataMember(Name = "os")]
    12.         public string OperationStatus { get; set; }
    13.  
    14.         [DataMember(Name = "od")]
    15.         public string OperationDetail { get; set; }
    16.  
    17.         //[DataMember(Name = "f")]
    18.         //public string FollowUpRequest { get; set; }
    19.  
    20.         /*public static RestResponse BuildResponse(bool status, string detail, string followup)
    21.         {
    22.             return new RestResponse { Status = Convert.ToInt16(status).ToString(), Detail = detail, FollowUpRequest = followup };
    23.         }*/
    24.  
    25.         public static RestResponse ServiceSuccess(string operationStatus = "1", string operationDetail = "")
    26.         {
    27.             return new RestResponse
    28.                 {
    29.                     ServiceStatus = "1",
    30.                     ServiceDetail = "",
    31.                     OperationStatus = operationStatus,
    32.                     OperationDetail = operationDetail
    33.                 };
    34.         }
    35.  
    36.         public static RestResponse ServiceFailure(string detail)
    37.         {
    38.             return new RestResponse { ServiceStatus = "0", ServiceDetail = detail };
    39.         }
    40.     }
    41.  
    Code (csharp):
    1.  
    2. public static class Streams
    3.     {
    4.         public static T Deserialize<T>(Stream stream)
    5.         {
    6.             T obj = Activator.CreateInstance<T>();
    7.             DataContractJsonSerializer serializer = new DataContractJsonSerializer(obj.GetType());
    8.             obj = (T)serializer.ReadObject(stream);
    9.             stream.Close();
    10.             return obj;
    11.         }
    12.  
    13.         public static string Serialize<T>(T obj)
    14.         {
    15.             DataContractJsonSerializer serializer = new DataContractJsonSerializer(obj.GetType());
    16.             MemoryStream ms = new MemoryStream();
    17.             serializer.WriteObject(ms, obj);
    18.             string retVal = Encoding.UTF8.GetString(ms.ToArray());
    19.             return retVal;
    20.         }
    21.  
    22.         public static byte[] GetMD5(Stream stream)
    23.         {
    24.             System.Security.Cryptography.HashAlgorithm algo = System.Security.Cryptography.MD5.Create();
    25.             return algo.ComputeHash(stream);
    26.         }
    27.     }
    28.