Browse Source

add client test sse prog

JAELYS 4 years ago
parent
commit
226d015c29

+ 16 - 0
Client/Client.csproj

@@ -0,0 +1,16 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp3.1</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="5.0.3" />
+    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
+    <PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" />
+    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="5.0.0" />
+    <PackageReference Include="Twilio" Version="5.55.0" />
+  </ItemGroup>
+
+</Project>

+ 67 - 0
Client/Program.cs

@@ -0,0 +1,67 @@
+using Client.SSE;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net.Http;
+using System.Security.Policy;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Threading;
+using System.Threading.Tasks;
+
+
+namespace Client
+{
+    class Program
+    {    
+        static void Main(string[] args)
+        {
+            //SSE
+            try
+            {
+                var cts = new CancellationTokenSource();
+                var header = new Dictionary<string, string>() { { "X-Auth-Name", "IES5" } };
+                //var sse = new EventSource(new Uri("https://localhost:5001/service/sse"), headeer, 5000);
+                var sse = new EventSource(new Uri("https://api2.teammodel.net/service/sse"), header, 5000);
+
+                sse.StateChanged += Sse_StateChanged;
+                sse.EventReceived += Sse_EventReceived;
+                sse.Start(cts.Token);
+
+                //cts?.Cancel(); 中斷SSE Client連線
+            }
+            catch (TaskCanceledException ex)
+            {
+                Console.WriteLine(ex.Message);
+            }
+            catch (AggregateException ex)
+            {
+                Console.WriteLine(ex.Message);
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine(ex.Message);
+            }
+            Console.ReadLine();
+        }
+
+        private static void Sse_EventReceived(object sender, ServerSentEventReceivedEventArgs e)
+        {
+            var mm = e.Message;
+            Console.WriteLine(mm.Data);
+        }
+
+
+        private static void Sse_StateChanged(object sender, StateChangedEventArgs e)
+        {
+            //CONNECTING 0 連線中
+            //OPEN 1 開啟
+            //CLOSED 2 關閉
+            var aa = e.State;
+
+            Console.WriteLine(aa.ToString());
+        }
+
+    }
+}

+ 143 - 0
Client/SSE/ConnectedState.cs

@@ -0,0 +1,143 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Net;
+using System.IO;
+using System.Threading.Tasks;
+using System.Threading;
+using System.Diagnostics;
+
+namespace Client.SSE
+{
+    public class ConnectedState : IConnectionState
+    {    
+
+        private IWebRequesterFactory mWebRequesterFactory;
+        private ServerSentEvent mSse = null;
+        private string mRemainingText = string.Empty;   // the text that is not ended with a lineending char is saved for next call.
+        private IServerResponse mResponse;
+        private Dictionary<string, string> headers;
+
+        public EventSourceState State { get { return EventSourceState.OPEN; } }
+
+        public ConnectedState(IServerResponse response, IWebRequesterFactory webRequesterFactory, Dictionary<string, string> headers)
+        {
+            mResponse = response;
+            mWebRequesterFactory = webRequesterFactory;
+            this.headers = headers;
+        }
+
+        public Task<IConnectionState> Run(Action<ServerSentEvent> msgReceived, CancellationToken cancelToken, Dictionary<string, string> headers)
+        {   
+            Task<IConnectionState> t = new Task<IConnectionState>(() =>
+            {
+                //using (mResponse)
+                {
+                    //using (var stream = mResponse.GetResponseStream())
+                    var stream = mResponse.GetResponseStream();
+                    {
+                        byte[] buffer = new byte[1024 * 8];
+                        var taskRead = stream.ReadAsync(buffer, 0, buffer.Length, cancelToken);
+
+                        try
+                        {
+                            taskRead.Wait(cancelToken);
+                        }
+                        catch (Exception ex)
+                        {
+                            Trace.WriteLine(ex, "ConnectedState.Run");
+                        }
+                        if (!cancelToken.IsCancellationRequested)
+                        {
+                            int bytesRead = taskRead.Result;
+                            if (bytesRead > 0) // stream has not reached the end yet
+                            {
+                                //Console.WriteLine("ReadCallback {0} bytesRead", bytesRead);
+                                string text = Encoding.UTF8.GetString(buffer, 0, bytesRead);
+                                text = mRemainingText + text;
+                                string[] lines = StringSplitter.SplitIntoLines(text, out mRemainingText);
+                                foreach (string line in lines)
+                                {
+                                    if (cancelToken.IsCancellationRequested) break;
+
+                                    // Dispatch message if empty lne
+                                    if (string.IsNullOrEmpty(line.Trim()) && mSse != null)
+                                    {
+                                        Trace.WriteLine("SSE Message received");
+                                        msgReceived(mSse);
+                                        mSse = null;
+                                    }
+                                    else if (line.StartsWith(":"))
+                                    {
+                                        // This a comment, just log it.
+                                        Trace.WriteLine("SSE A comment was received: " + line);
+                                    }
+                                    else
+                                    {
+                                        string fieldName = String.Empty;
+                                        string fieldValue = String.Empty;
+                                        if (line.Contains(':'))
+                                        {
+                                            int index = line.IndexOf(':');
+                                            fieldName = line.Substring(0, index);
+                                            fieldValue = line.Substring(index + 1).TrimStart();
+                                        }
+                                        else
+                                            fieldName = line;
+
+                                        if (String.Compare(fieldName, "event", true) == 0)
+                                        {
+                                            mSse = mSse ?? new ServerSentEvent();
+                                            mSse.EventType = fieldValue;
+                                        }
+                                        else if (String.Compare(fieldName, "data", true) == 0)
+                                        {
+                                            mSse = mSse ?? new ServerSentEvent();
+                                            mSse.Data = fieldValue + '\n';
+                                        }
+                                        else if (String.Compare(fieldName, "id", true) == 0)
+                                        {
+                                            mSse = mSse ?? new ServerSentEvent();
+                                            mSse.LastEventId = fieldValue;
+                                        }
+                                        else if (String.Compare(fieldName, "retry", true) == 0)
+                                        {
+                                            int parsedRetry;
+                                            if (int.TryParse(fieldValue, out parsedRetry))
+                                            {
+                                                mSse = mSse ?? new ServerSentEvent();
+                                                mSse.Retry = parsedRetry;
+                                            }
+                                        }
+                                        else
+                                        {
+                                            // Ignore this, just log it
+                                            Trace.WriteLine("SSE A unknown line was received: " + line);
+                                        }
+                                    }
+                                }
+
+                                if (!cancelToken.IsCancellationRequested)
+                                    return this;
+                            }
+                            else // end of the stream reached
+                            {
+                                Trace.WriteLine("SSE No bytes read. End of stream.");
+                            }
+                        }
+
+                        //stream.Dispose()
+                        //stream.Close();
+                        //mResponse.Close();
+                        //mResponse.Dispose();
+                        return new DisconnectedState(mResponse.ResponseUri, mWebRequesterFactory, headers);
+                    }
+                }
+            });
+
+            t.Start();
+            return t;
+        }
+    }
+}

+ 55 - 0
Client/SSE/ConnectingState.cs

@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Net;
+using System.IO;
+using System.Threading;
+using System.Diagnostics;
+
+namespace Client.SSE
+{
+    public class ConnectingState : IConnectionState
+    {      
+
+        private Uri mUrl;
+        private IWebRequesterFactory mWebRequesterFactory;
+        private Dictionary<string, string> headers;
+
+        public EventSourceState State { get { return EventSourceState.CONNECTING; } }
+
+        public ConnectingState(Uri url, IWebRequesterFactory webRequesterFactory, Dictionary<string, string> headers)
+        {
+            if (url == null) throw new ArgumentNullException("Url cant be null");
+            if (webRequesterFactory == null) throw new ArgumentNullException("Factory cant be null");
+            mUrl = url;
+            mWebRequesterFactory = webRequesterFactory;
+            this.headers = headers;
+        }
+
+        public Task<IConnectionState> Run(Action<ServerSentEvent> donothing, CancellationToken cancelToken, Dictionary<string, string> headers)
+        {
+            IWebRequester requester = mWebRequesterFactory.Create();
+            var taskResp = requester.Get(mUrl, headers);
+
+            return taskResp.ContinueWith<IConnectionState>(tsk => 
+            {
+                if (tsk.Status == TaskStatus.RanToCompletion && !cancelToken.IsCancellationRequested)
+                {
+                    IServerResponse response = tsk.Result;
+                    if (response.StatusCode == HttpStatusCode.OK)
+                    {
+                        return new ConnectedState(response, mWebRequesterFactory, headers);
+                    }
+                    else
+                    {
+                        Trace.WriteLine("Failed to connect to: " + mUrl.ToString() + response ?? (" Http statuscode: " + response.StatusCode));
+                    }
+                }
+
+                return new DisconnectedState(mUrl, mWebRequesterFactory, headers);
+            });
+        }
+    }
+}

+ 37 - 0
Client/SSE/DisconnectedState.cs

@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Client.SSE
+{
+    public class DisconnectedState : IConnectionState 
+    {
+        private Uri mUrl;
+        private IWebRequesterFactory mWebRequesterFactory;
+        private Dictionary<string, string> headers;
+
+        public EventSourceState State
+        {
+            get { return EventSourceState.CLOSED; }
+        }
+
+        public DisconnectedState(Uri url, IWebRequesterFactory webRequesterFactory, Dictionary<string, string> headers)
+        {
+            if (url == null) throw new ArgumentNullException("Url cant be null");
+            mUrl = url;
+            mWebRequesterFactory = webRequesterFactory;
+            this.headers = headers;
+        }
+
+        public Task<IConnectionState> Run(Action<ServerSentEvent> donothing, CancellationToken cancelToken, Dictionary<string, string> headers)
+        {
+            if(cancelToken.IsCancellationRequested)
+                return Task.Factory.StartNew<IConnectionState>(() => { return new DisconnectedState(mUrl, mWebRequesterFactory, headers); });
+            else
+                return Task.Factory.StartNew<IConnectionState>(() => { return new ConnectingState(mUrl, mWebRequesterFactory, headers); });
+        }
+    }
+}

+ 116 - 0
Client/SSE/EventSource.cs

@@ -0,0 +1,116 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Text;
+using System.Threading;
+
+namespace Client.SSE
+{
+    public class EventSource
+    {   
+        public event EventHandler<StateChangedEventArgs> StateChanged;
+        public event EventHandler<ServerSentEventReceivedEventArgs> EventReceived;
+
+        public CancellationTokenSource CancellationToken { get; set; }
+
+        private IWebRequesterFactory _webRequesterFactory = new WebRequesterFactory();
+        private int _timeout = 0;
+        public Uri Url { get; private set; }
+        public EventSourceState State { get { return CurrentState.State; } }
+        public string LastEventId { get; private set; }
+        private IConnectionState mCurrentState = null;
+        private CancellationToken mStopToken;
+        private CancellationTokenSource mTokenSource = new CancellationTokenSource();
+        private Dictionary<string, string> _headers;
+
+        private IConnectionState CurrentState
+        {
+            get { return mCurrentState; }
+            set
+            {
+                if (!value.Equals(mCurrentState))
+                {
+                    StringBuilder sb = new StringBuilder("State changed from ");
+                    sb.Append(mCurrentState == null ? "Unknown" : mCurrentState.State.ToString());
+                    sb.Append(" to ");
+                    sb.Append(value == null ? "Unknown" : value.State.ToString());
+                    Trace.WriteLine(sb.ToString());
+                    mCurrentState = value;
+                    OnStateChanged(mCurrentState.State);
+                }
+            }
+        }
+
+        public EventSource(Uri url, int timeout)
+        {
+            Initialize(url, timeout);
+        }
+
+        public EventSource(Uri url, Dictionary<string, string> headers, int timeout)
+        {
+            _headers = headers;
+            Initialize(url, timeout);
+        }
+
+        /// <summary>
+        /// Constructor for testing purposes
+        /// </summary>
+        /// <param name="factory">The factory that generates the WebRequester to use.</param>
+        public EventSource(Uri url, IWebRequesterFactory factory)
+        {
+            _webRequesterFactory = factory;
+            Initialize(url, 0);
+        }
+
+        private void Initialize(Uri url, int timeout)
+        {
+            _timeout = timeout;
+            Url = url;
+            CurrentState = new DisconnectedState(Url, _webRequesterFactory, _headers);
+            Trace.WriteLine("SSE EventSource created for " + url.ToString());
+        }
+
+
+        /// <summary>
+        /// Start the EventSource. 
+        /// </summary>
+        /// <param name="stopToken">Cancel this token to stop the EventSource.</param>
+        public void Start(CancellationToken stopToken)
+        {
+            if (State == EventSourceState.CLOSED)
+            {
+                mStopToken = stopToken;
+                mTokenSource = CancellationTokenSource.CreateLinkedTokenSource(stopToken);
+                Run();
+            }
+        }
+
+        protected void Run()
+        {
+            if (mTokenSource.IsCancellationRequested && CurrentState.State == EventSourceState.CLOSED)
+                return;
+
+            mCurrentState.Run(this.OnEventReceived, mTokenSource.Token, _headers).ContinueWith(cs =>
+            {
+                CurrentState = cs.Result;
+                Run();
+            });
+        }
+
+        protected void OnEventReceived(ServerSentEvent sse)
+        {
+            if (EventReceived != null)
+            {
+                EventReceived(this, new ServerSentEventReceivedEventArgs(sse));
+            }
+        }
+
+        protected void OnStateChanged(EventSourceState newState)
+        {
+            if (StateChanged != null)
+            {
+                StateChanged(this, new StateChangedEventArgs(newState));
+            }
+        }
+    }
+}

+ 14 - 0
Client/SSE/EventSourceState.cs

@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Client.SSE
+{
+    public enum EventSourceState
+    {
+        CONNECTING,
+        OPEN,
+        CLOSED 
+    }
+}

+ 15 - 0
Client/SSE/IConnectionState.cs

@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Client.SSE
+{
+    public interface IConnectionState
+    {
+        EventSourceState State { get; }
+        Task<IConnectionState> Run(Action<ServerSentEvent> MsgReceivedCallback, CancellationToken cancelToken, Dictionary<string, string> headers);
+    }
+}

+ 17 - 0
Client/SSE/IServerResponse.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Text;
+
+namespace Client.SSE
+{
+    public interface IServerResponse
+    {
+        HttpStatusCode StatusCode { get; }
+
+        System.IO.Stream GetResponseStream();
+
+        Uri ResponseUri { get; }
+    }
+}

+ 15 - 0
Client/SSE/IWebRequester.cs

@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Client.SSE
+{
+    public interface IWebRequester
+    {
+        Task<IServerResponse> Get(Uri url, Dictionary<string, string> headers = null);
+
+    }
+}

+ 12 - 0
Client/SSE/IWebRequesterFactory.cs

@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Client.SSE
+{
+    public interface IWebRequesterFactory
+    {
+        IWebRequester Create();
+    }
+}

+ 39 - 0
Client/SSE/ServerResponse.cs

@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Text;
+
+namespace Client.SSE
+{
+    public class ServerResponse : IServerResponse
+    {
+        private System.Net.HttpWebResponse mHttpResponse;
+
+        public ServerResponse(System.Net.WebResponse webResponse)
+        {
+            this.mHttpResponse = webResponse as HttpWebResponse;
+        }
+
+        public HttpStatusCode StatusCode
+        {
+            get
+            {
+                return mHttpResponse.StatusCode;
+            }
+        }
+
+        public System.IO.Stream GetResponseStream()
+        {
+            return mHttpResponse.GetResponseStream();
+        }
+
+        public Uri ResponseUri
+        {
+            get
+            {
+                return mHttpResponse.ResponseUri;
+            }
+        }
+    }
+}

+ 26 - 0
Client/SSE/ServerSentEvent.cs

@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Client.SSE
+{
+    public class ServerSentEvent
+    {
+        public string LastEventId { get; set; }
+        public string EventType { get; set; }
+        public string Data { get; set; }
+        public int? Retry { get; set; }
+
+        public override string ToString()
+        {
+            StringBuilder sb = new StringBuilder();
+            sb.Append("EventType: ").Append(EventType).AppendLine();
+            sb.Append("Data: ").Append(Data).AppendLine();
+            sb.Append("LastEventId: ").Append(LastEventId).AppendLine();
+            if(Retry.HasValue)
+                sb.Append("Retry: ").Append(Retry.Value).AppendLine();
+            return sb.ToString();
+        }
+    }
+}

+ 17 - 0
Client/SSE/ServerSentEventReceivedEventArgs.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Client.SSE
+{
+    public class ServerSentEventReceivedEventArgs : EventArgs
+    {
+        public ServerSentEvent Message { get; private set; }
+        public ServerSentEventReceivedEventArgs(ServerSentEvent message)
+        {
+            Message = message;
+        }
+
+    }
+}

+ 16 - 0
Client/SSE/StateChangedEventArgs.cs

@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Client.SSE
+{
+    public class StateChangedEventArgs : EventArgs
+    {
+        public EventSourceState State { get; private set; }
+        public StateChangedEventArgs(EventSourceState state)
+        {
+            State = state;
+        }
+    }
+}

+ 47 - 0
Client/SSE/StringSplitter.cs

@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Client.SSE
+{
+    public class StringSplitter
+    {
+        public static string[] SplitIntoLines(string text, out string remainingText)
+        {
+            List<string> lines = new List<string>();
+
+            //bool endFound = false;
+            //bool searchingForFirstChar = true;
+            int lineLength = 0;
+            char previous = char.MinValue;
+            for (int i = 0; i < text.Length; i++)
+            {
+                char c = text[i];
+                if (c == '\n' || c == '\r')
+                {
+                    bool isCRLFPair = previous=='\r' && c == '\n';
+
+                    if (!isCRLFPair)
+                    {
+                        string line = text.Substring(i - lineLength, lineLength);
+                        lines.Add(line);
+                    }
+
+                    lineLength = 0;
+                }
+                else
+                {
+                    lineLength++;
+                }
+                previous = c;
+            }
+
+            // Save the last chars that is not followed by a lineending.
+            remainingText = text.Substring(text.Length - lineLength);
+
+            return lines.ToArray();
+        }
+    }
+}

+ 38 - 0
Client/SSE/WatchDog.cs

@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Client.SSE
+{
+    public class Watchdog
+    {
+        private long _timeout;
+        private Timer _timer;
+        public event EventHandler TimerExpired;
+
+        public void Start()
+        {
+            _timer = new Timer(new TimerCallback(OnTimerExpired), null, 0, _timeout);
+        }
+
+        public void Reset()
+        {
+            _timer.Change(0, _timeout);
+        }
+
+        private void OnTimerExpired(object State)
+        {
+            _timer.Change(Timeout.Infinite, Timeout.Infinite);
+            if (TimerExpired != null)
+                TimerExpired(this, new EventArgs());
+        }
+
+        public Watchdog(long timeout)
+        {
+            if (timeout < 1) throw new ArgumentOutOfRangeException("timeout", "timeout muste be greater than zero.");
+        }
+    }
+}

+ 30 - 0
Client/SSE/WebRequester.cs

@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Threading.Tasks;
+
+namespace Client.SSE
+{
+    public class WebRequester : IWebRequester
+    {
+        public Task<IServerResponse> Get(Uri url, Dictionary<string, string> headers = null)
+        {
+            var wreq = (HttpWebRequest)WebRequest.Create(url);
+            wreq.Method = "GET";
+            wreq.Proxy = null;
+
+            if (headers != null)
+            {
+                foreach (var header in headers)
+                {
+                    wreq.Headers.Add(header.Key, header.Value);
+                }
+            }
+
+            var taskResp = Task.Factory.FromAsync<WebResponse>(wreq.BeginGetResponse,
+                wreq.EndGetResponse, null).ContinueWith<IServerResponse>(t => new ServerResponse(t.Result));
+            return taskResp;
+
+        }
+    }
+}

+ 16 - 0
Client/SSE/WebRequesterFactory.cs

@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Client.SSE
+{
+    public class WebRequesterFactory : IWebRequesterFactory
+    {
+        public IWebRequester Create()
+        {
+            return new WebRequester();
+        }
+    }
+}

+ 7 - 1
TEAMModelOS.sln

@@ -11,7 +11,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TEAMModelGrpc", "TEAMModelG
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TEAMModelFunction", "TEAMModelFunction\TEAMModelFunction.csproj", "{78470113-6261-4F9A-9EF3-E315F060813D}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TEAMModelAPI", "TEAMModelAPI\TEAMModelAPI.csproj", "{2146FEEC-7178-4141-A8C7-CBEBAEE404A6}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TEAMModelAPI", "TEAMModelAPI\TEAMModelAPI.csproj", "{2146FEEC-7178-4141-A8C7-CBEBAEE404A6}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Client", "Client\Client.csproj", "{E1A7F6EA-319E-4582-A800-A04DEB8284D9}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -39,6 +41,10 @@ Global
 		{2146FEEC-7178-4141-A8C7-CBEBAEE404A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{2146FEEC-7178-4141-A8C7-CBEBAEE404A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{2146FEEC-7178-4141-A8C7-CBEBAEE404A6}.Release|Any CPU.Build.0 = Release|Any CPU
+		{E1A7F6EA-319E-4582-A800-A04DEB8284D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{E1A7F6EA-319E-4582-A800-A04DEB8284D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{E1A7F6EA-319E-4582-A800-A04DEB8284D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{E1A7F6EA-319E-4582-A800-A04DEB8284D9}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE