Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Moved old UserSession into a deprecated namespace to let users know t…

…here's a new supported Session/OAuth forthcoming.

ServiceBase now implements IServiceBase to allow for additional functionality to be added to via extension methods instead of inheritance.

Allow Adhoc Services to be defined/registered in Configure()

All Session/OAuth functionality are added via extension methods.
Merged Twitter OAuth service (based on @migueldeicaza's OAuthAuthorizer) into ServicesInterface project.
Added SessionFeature.Register() to ensure permanent and browser based session cookies are always set.
Added an Extensible IOAuthSession which supports being implemented by a custom user-defined type.
OAuth/Session uses any ICacheClient if defined, falls back to IRedisClientsManager which then fallsback to MemoryCacheClient.
  • Loading branch information...
commit 8fb8cc6437f0e23a566ba9afbd6a7ec675dade90 1 parent e2ab673
@mythz mythz authored
Showing with 910 additions and 48 deletions.
  1. BIN  NuGet/ServiceStack/lib/ServiceStack.Text.dll
  2. +1 −1  build/build.bat
  3. BIN  lib/ServiceStack.Text.dll
  4. +2 −0  src/ServiceStack.Common/Web/HttpResult.cs
  5. +8 −0 src/ServiceStack.ServiceInterface/AuthenticateAttribute.cs
  6. +1 −1  src/ServiceStack.ServiceInterface/{Session → Deprecated.Session}/CachedUserSessionManager.cs
  7. +1 −1  src/ServiceStack.ServiceInterface/{Session → Deprecated.Session}/IUserSessionManager.cs
  8. +1 −1  src/ServiceStack.ServiceInterface/{Session → Deprecated.Session}/PublicAndPrivateClientSessions.cs
  9. +1 −1  src/ServiceStack.ServiceInterface/{Session → Deprecated.Session}/UserClientSession.cs
  10. +1 −1  src/ServiceStack.ServiceInterface/{Session → Deprecated.Session}/UserSession.cs
  11. +21 −0 src/ServiceStack.ServiceInterface/IServiceBase.cs
  12. +21 −0 src/ServiceStack.ServiceInterface/OAuth/IOAuthSession.cs
  13. +332 −0 src/ServiceStack.ServiceInterface/OAuth/OAuthAuthorizer.cs
  14. +32 −0 src/ServiceStack.ServiceInterface/OAuth/OAuthConfig.cs
  15. +122 −0 src/ServiceStack.ServiceInterface/OAuth/OAuthService.cs
  16. +58 −0 src/ServiceStack.ServiceInterface/OAuth/OAuthUserSession.cs
  17. +2 −3 src/ServiceStack.ServiceInterface/ServiceBase.cs
  18. +106 −0 src/ServiceStack.ServiceInterface/ServiceExtensions.cs
  19. +14 −5 src/ServiceStack.ServiceInterface/ServiceStack.ServiceInterface.csproj
  20. +33 −0 src/ServiceStack.ServiceInterface/SessionFeature.cs
  21. +5 −0 src/ServiceStack.ServiceInterface/Testing/BasicAppHost.cs
  22. +5 −0 src/ServiceStack.ServiceInterface/Testing/TestAppHost.cs
  23. +12 −0 src/ServiceStack/AppHostExtensions.cs
  24. +4 −0 src/ServiceStack/CacheAccess.Providers/MemoryCacheClient.cs
  25. +30 −0 src/ServiceStack/Configuration/ConfigurationResourceManager.cs
  26. +27 −22 src/ServiceStack/ServiceHost/ServiceController.cs
  27. +22 −2 src/ServiceStack/ServiceHost/ServiceManager.cs
  28. +1 −0  src/ServiceStack/ServiceStack.csproj
  29. +14 −5 src/ServiceStack/WebHost.EndPoints/AppHostBase.cs
  30. +2 −0  src/ServiceStack/WebHost.EndPoints/IAppHost.cs
  31. +14 −5 src/ServiceStack/WebHost.EndPoints/Support/HttpListenerBase.cs
  32. +8 −0 tests/ServiceStack.Common.Tests/MessagingTests.cs
  33. +4 −0 tests/ServiceStack.Common.Tests/ServiceStack.Common.Tests.csproj
  34. +5 −0 tests/ServiceStack.ServiceHost.Tests/Formats/ViewTests.cs
View
BIN  NuGet/ServiceStack/lib/ServiceStack.Text.dll
Binary file not shown
View
2  build/build.bat
@@ -7,12 +7,12 @@ SET BUILD=Release
COPY ..\src\RazorEngine\bin\%BUILD%\*.* ..\NuGet\ServiceStack\lib
COPY ..\src\ServiceStack.ServiceInterface\bin\%BUILD%\*.* ..\NuGet\ServiceStack\lib
+COPY ..\src\ServiceStack.ServiceInterface\bin\%BUILD%\*.* ..\..\chaweet\api\lib
COPY ..\src\RazorEngine\bin\%BUILD%\*.* ..\..\ServiceStack.Examples\lib
COPY ..\src\ServiceStack.ServiceInterface\bin\%BUILD%\*.* ..\..\ServiceStack.Examples\lib
COPY ..\src\ServiceStack\bin\%BUILD%\*.* ..\..\ServiceStack.Contrib\lib
COPY ..\src\ServiceStack\bin\%BUILD%\*.* ..\..\ServiceStack.RedisWebServices\lib
-COPY ..\src\ServiceStack\bin\%BUILD%\*.* ..\..\chaweet\api\lib
COPY ..\src\ServiceStack\bin\%BUILD%\ServiceStack.Interfaces.dll ..\..\ServiceStack.Redis\lib
COPY ..\src\ServiceStack\bin\%BUILD%\ServiceStack.Text.dll ..\..\ServiceStack.Redis\lib
View
BIN  lib/ServiceStack.Text.dll
Binary file not shown
View
2  src/ServiceStack.Common/Web/HttpResult.cs
@@ -200,5 +200,7 @@ public static HttpResult Status201Created(object response, string newLocationUri
}
};
}
+
+
}
}
View
8 src/ServiceStack.ServiceInterface/AuthenticateAttribute.cs
@@ -0,0 +1,8 @@
+using System;
+
+namespace ServiceStack.ServiceInterface
+{
+ public class AuthenticateAttribute : Attribute
+ {
+ }
+}
View
2  ...rface/Session/CachedUserSessionManager.cs → ...cated.Session/CachedUserSessionManager.cs
@@ -14,7 +14,7 @@
using ServiceStack.CacheAccess;
using ServiceStack.Logging;
-namespace ServiceStack.ServiceInterface.Session
+namespace ServiceStack.ServiceInterface.Deprecated.Session
{
/// <summary>
/// Manages all the User Sessions into the ICacheClient provided
View
2  ...eInterface/Session/IUserSessionManager.cs → ...Deprecated.Session/IUserSessionManager.cs
@@ -11,7 +11,7 @@
using System;
using System.Collections.Generic;
-namespace ServiceStack.ServiceInterface.Session
+namespace ServiceStack.ServiceInterface.Deprecated.Session
{
/// <summary>
/// Manager Interface listing all the methods required to manage a users session.
View
2  ...Session/PublicAndPrivateClientSessions.cs → ...Session/PublicAndPrivateClientSessions.cs
@@ -9,7 +9,7 @@
*/
-namespace ServiceStack.ServiceInterface.Session
+namespace ServiceStack.ServiceInterface.Deprecated.Session
{
/// <summary>
/// Holds a 'Secure' and 'Unsecure' client session for the user.
View
2  ...iceInterface/Session/UserClientSession.cs → ...e/Deprecated.Session/UserClientSession.cs
@@ -11,7 +11,7 @@
using System;
using ServiceStack.DesignPatterns.Model;
-namespace ServiceStack.ServiceInterface.Session
+namespace ServiceStack.ServiceInterface.Deprecated.Session
{
/// <summary>
/// Holds information on a single 'User Client' session.
View
2  ...k.ServiceInterface/Session/UserSession.cs → ...terface/Deprecated.Session/UserSession.cs
@@ -12,7 +12,7 @@
using System.Collections.Generic;
using ServiceStack.Common;
-namespace ServiceStack.ServiceInterface.Session
+namespace ServiceStack.ServiceInterface.Deprecated.Session
{
/// <summary>
/// Holds all the data required for a User Session
View
21 src/ServiceStack.ServiceInterface/IServiceBase.cs
@@ -0,0 +1,21 @@
+using ServiceStack.ServiceHost;
+using ServiceStack.WebHost.Endpoints;
+
+namespace ServiceStack.ServiceInterface
+{
+ public interface IServiceBase
+ {
+ IAppHost AppHost { get; set; }
+
+ /// <summary>
+ /// Resolve an alternate Web Service from ServiceStack's IOC container.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <returns></returns>
+ T ResolveService<T>();
+
+ T TryResolve<T>();
+
+ IRequestContext RequestContext { get; }
+ }
+}
View
21 src/ServiceStack.ServiceInterface/OAuth/IOAuthSession.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+
+namespace ServiceStack.ServiceInterface.OAuth
+{
+ public interface IOAuthSession
+ {
+ string ReferrerUrl { get; set; }
+ string Id { get; set; }
+ string OAuthToken { get; set; }
+ string AccessToken { get; set; }
+ string RequestToken { get; set; }
+ string RequestTokenSecret { get; set; }
+ DateTime CreatedAt { get; set; }
+ DateTime LastModified { get; set; }
+ bool IsAuthorized();
+ Dictionary<string, string> Items { get; }
+
+ void OnAuthenticated(OAuthService oAuthService, Dictionary<string, string> authInfo);
+ }
+}
View
332 src/ServiceStack.ServiceInterface/OAuth/OAuthAuthorizer.cs
@@ -0,0 +1,332 @@
+//
+// OAuth framework for TweetStation
+//
+// Author;
+// Miguel de Icaza (miguel@gnome.org)
+//
+// Possible optimizations:
+// Instead of sorting every time, keep things sorted
+// Reuse the same dictionary, update the values
+//
+// Copyright 2010 Miguel de Icaza
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Net;
+using System.Web;
+using System.Security.Cryptography;
+using ServiceStack.ServiceModel;
+
+namespace ServiceStack.ServiceInterface.OAuth
+{
+ //
+ // Configuration information for an OAuth client
+ //
+ //public class OAuthConfig {
+ // keys, callbacks
+ //public string ConsumerKey, Callback, ConsumerSecret;
+
+ // Urls
+ //public string RequestTokenUrl, AccessTokenUrl, AuthorizeUrl;
+ //}
+
+ //
+ // The authorizer uses a config and an optional xAuth user/password
+ // to perform the OAuth authorization process as well as signing
+ // outgoing http requests
+ //
+ // To get an access token, you use these methods in the workflow:
+ // AcquireRequestToken
+ // AuthorizeUser
+ //
+ // These static methods only require the access token:
+ // AuthorizeRequest
+ // AuthorizeTwitPic
+ //
+ public class OAuthAuthorizer
+ {
+ // Settable by the user
+ public string xAuthUsername, xAuthPassword;
+
+ OAuthConfig config;
+ public string RequestToken, RequestTokenSecret;
+ public string AuthorizationToken, AuthorizationVerifier;
+ public string AccessToken, AccessTokenSecret;//, AccessScreenName;
+ //public long AccessId;
+ public Dictionary<string, string> AuthInfo = new Dictionary<string, string>();
+
+ // Constructor for standard OAuth
+ public OAuthAuthorizer(OAuthConfig config)
+ {
+ this.config = config;
+ }
+
+ static Random random = new Random();
+ static DateTime UnixBaseTime = new DateTime(1970, 1, 1);
+
+ // 16-byte lower-case or digit string
+ static string MakeNonce()
+ {
+ var ret = new char[16];
+ for (int i = 0; i < ret.Length; i++)
+ {
+ int n = random.Next(35);
+ if (n < 10)
+ ret[i] = (char)(n + '0');
+ else
+ ret[i] = (char)(n - 10 + 'a');
+ }
+ return new string(ret);
+ }
+
+ static string MakeTimestamp()
+ {
+ return ((long)(DateTime.UtcNow - UnixBaseTime).TotalSeconds).ToString();
+ }
+
+ // Makes an OAuth signature out of the HTTP method, the base URI and the headers
+ static string MakeSignature(string method, string base_uri, Dictionary<string, string> headers)
+ {
+ var items = from k in headers.Keys
+ orderby k
+ select k + "%3D" + OAuthUtils.PercentEncode(headers[k]);
+
+ return method + "&" + OAuthUtils.PercentEncode(base_uri) + "&" +
+ string.Join("%26", items.ToArray());
+ }
+
+ static string MakeSigningKey(string consumerSecret, string oauthTokenSecret)
+ {
+ return OAuthUtils.PercentEncode(consumerSecret) + "&" + (oauthTokenSecret != null ? OAuthUtils.PercentEncode(oauthTokenSecret) : "");
+ }
+
+ static string MakeOAuthSignature(string compositeSigningKey, string signatureBase)
+ {
+ var sha1 = new HMACSHA1(Encoding.UTF8.GetBytes(compositeSigningKey));
+
+ return Convert.ToBase64String(sha1.ComputeHash(Encoding.UTF8.GetBytes(signatureBase)));
+ }
+
+ static string HeadersToOAuth(Dictionary<string, string> headers)
+ {
+ return "OAuth " + String.Join(",", (from x in headers.Keys select String.Format("{0}=\"{1}\"", x, headers[x])).ToArray());
+ }
+
+ public bool AcquireRequestToken()
+ {
+ var headers = new Dictionary<string, string>() {
+ { "oauth_callback", OAuthUtils.PercentEncode (config.CallbackUrl) },
+ { "oauth_consumer_key", config.ConsumerKey },
+ { "oauth_nonce", MakeNonce () },
+ { "oauth_signature_method", "HMAC-SHA1" },
+ { "oauth_timestamp", MakeTimestamp () },
+ { "oauth_version", "1.0" }};
+
+ string signature = MakeSignature("POST", config.RequestTokenUrl, headers);
+ string compositeSigningKey = MakeSigningKey(config.ConsumerSecret, null);
+ string oauth_signature = MakeOAuthSignature(compositeSigningKey, signature);
+
+ var wc = new WebClient();
+ headers.Add("oauth_signature", OAuthUtils.PercentEncode(oauth_signature));
+ wc.Headers[HttpRequestHeader.Authorization] = HeadersToOAuth(headers);
+
+ try
+ {
+ var result = HttpUtility.ParseQueryString(wc.UploadString(new Uri(config.RequestTokenUrl), ""));
+
+ if (result["oauth_callback_confirmed"] != null)
+ {
+ RequestToken = result["oauth_token"];
+ RequestTokenSecret = result["oauth_token_secret"];
+
+ return true;
+ }
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ // fallthrough for errors
+ }
+ return false;
+ }
+
+ // Invoked after the user has authorized us
+ //
+ // TODO: this should return the stream error for invalid passwords instead of
+ // just true/false.
+ public bool AcquireAccessToken()
+ {
+ var headers = new Dictionary<string, string>() {
+ { "oauth_consumer_key", config.ConsumerKey },
+ { "oauth_nonce", MakeNonce () },
+ { "oauth_signature_method", "HMAC-SHA1" },
+ { "oauth_timestamp", MakeTimestamp () },
+ { "oauth_version", "1.0" }};
+ var content = "";
+ if (xAuthUsername == null)
+ {
+ headers.Add("oauth_token", OAuthUtils.PercentEncode(AuthorizationToken));
+ headers.Add("oauth_verifier", OAuthUtils.PercentEncode(AuthorizationVerifier));
+ }
+ else
+ {
+ headers.Add("x_auth_username", OAuthUtils.PercentEncode(xAuthUsername));
+ headers.Add("x_auth_password", OAuthUtils.PercentEncode(xAuthPassword));
+ headers.Add("x_auth_mode", "client_auth");
+ content = String.Format("x_auth_mode=client_auth&x_auth_password={0}&x_auth_username={1}", OAuthUtils.PercentEncode(xAuthPassword), OAuthUtils.PercentEncode(xAuthUsername));
+ }
+
+ string signature = MakeSignature("POST", config.AccessTokenUrl, headers);
+ string compositeSigningKey = MakeSigningKey(config.ConsumerSecret, RequestTokenSecret);
+ string oauth_signature = MakeOAuthSignature(compositeSigningKey, signature);
+
+ var wc = new WebClient();
+ headers.Add("oauth_signature", OAuthUtils.PercentEncode(oauth_signature));
+ if (xAuthUsername != null)
+ {
+ headers.Remove("x_auth_username");
+ headers.Remove("x_auth_password");
+ headers.Remove("x_auth_mode");
+ }
+ wc.Headers[HttpRequestHeader.Authorization] = HeadersToOAuth(headers);
+
+ try
+ {
+ var result = HttpUtility.ParseQueryString(wc.UploadString(new Uri(config.AccessTokenUrl), content));
+
+ if (result["oauth_token"] != null)
+ {
+ AccessToken = result["oauth_token"];
+ AccessTokenSecret = result["oauth_token_secret"];
+ AuthInfo = result.ToDictionary();
+
+ return true;
+ }
+ }
+ catch (WebException e)
+ {
+ var x = e.Response.GetResponseStream();
+ var j = new System.IO.StreamReader(x);
+ Console.WriteLine(j.ReadToEnd());
+ Console.WriteLine(e);
+ // fallthrough for errors
+ }
+ return false;
+ }
+
+ //
+ // Assign the result to the Authorization header, like this:
+ // request.Headers [HttpRequestHeader.Authorization] = AuthorizeRequest (...)
+ //
+ public static string AuthorizeRequest(OAuthConfig config, string oauthToken, string oauthTokenSecret, string method, Uri uri, string data)
+ {
+ var headers = new Dictionary<string, string>() {
+ { "oauth_consumer_key", config.ConsumerKey },
+ { "oauth_nonce", MakeNonce () },
+ { "oauth_signature_method", "HMAC-SHA1" },
+ { "oauth_timestamp", MakeTimestamp () },
+ { "oauth_token", oauthToken },
+ { "oauth_version", "1.0" }};
+ var signatureHeaders = new Dictionary<string, string>(headers);
+
+ // Add the data and URL query string to the copy of the headers for computing the signature
+ if (data != null && data != "")
+ {
+ var parsed = HttpUtility.ParseQueryString(data);
+ foreach (string k in parsed.Keys)
+ {
+ signatureHeaders.Add(k, OAuthUtils.PercentEncode(parsed[k]));
+ }
+ }
+
+ var nvc = HttpUtility.ParseQueryString(uri.Query);
+ foreach (string key in nvc)
+ {
+ if (key != null)
+ signatureHeaders.Add(key, OAuthUtils.PercentEncode(nvc[key]));
+ }
+
+ string signature = MakeSignature(method, uri.GetLeftPart(UriPartial.Path), signatureHeaders);
+ string compositeSigningKey = MakeSigningKey(config.ConsumerSecret, oauthTokenSecret);
+ string oauth_signature = MakeOAuthSignature(compositeSigningKey, signature);
+
+ headers.Add("oauth_signature", OAuthUtils.PercentEncode(oauth_signature));
+
+ return HeadersToOAuth(headers);
+ }
+
+ //
+ // Used to authorize an HTTP request going to TwitPic
+ //
+ public static void AuthorizeTwitPic(OAuthConfig config, HttpWebRequest wc, string oauthToken, string oauthTokenSecret)
+ {
+ var headers = new Dictionary<string, string>() {
+ { "oauth_consumer_key", config.ConsumerKey },
+ { "oauth_nonce", MakeNonce () },
+ { "oauth_signature_method", "HMAC-SHA1" },
+ { "oauth_timestamp", MakeTimestamp () },
+ { "oauth_token", oauthToken },
+ { "oauth_version", "1.0" },
+ //{ "realm", "http://api.twitter.com" }
+ };
+ string signurl = "http://api.twitter.com/1/account/verify_credentials.xml";
+ // The signature is not done against the *actual* url, it is done against the verify_credentials.json one
+ string signature = MakeSignature("GET", signurl, headers);
+ string compositeSigningKey = MakeSigningKey(config.ConsumerSecret, oauthTokenSecret);
+ string oauth_signature = MakeOAuthSignature(compositeSigningKey, signature);
+
+ headers.Add("oauth_signature", OAuthUtils.PercentEncode(oauth_signature));
+
+ //Util.Log ("Headers: " + HeadersToOAuth (headers));
+ wc.Headers.Add("X-Verify-Credentials-Authorization", HeadersToOAuth(headers));
+ wc.Headers.Add("X-Auth-Service-Provider", signurl);
+ }
+ }
+
+ public static class OAuthUtils
+ {
+
+ //
+ // This url encoder is different than regular Url encoding found in .NET
+ // as it is used to compute the signature based on a url. Every document
+ // on the web omits this little detail leading to wasting everyone's time.
+ //
+ // This has got to be one of the lamest specs and requirements ever produced
+ //
+ public static string PercentEncode(string s)
+ {
+ var sb = new StringBuilder();
+
+ foreach (byte c in Encoding.UTF8.GetBytes(s))
+ {
+ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~')
+ sb.Append((char)c);
+ else
+ {
+ sb.AppendFormat("%{0:X2}", c);
+ }
+ }
+ return sb.ToString();
+ }
+ }
+}
View
32 src/ServiceStack.ServiceInterface/OAuth/OAuthConfig.cs
@@ -0,0 +1,32 @@
+using ServiceStack.Configuration;
+
+namespace ServiceStack.ServiceInterface.OAuth
+{
+ public class OAuthConfig
+ {
+ public static string OAuthRealm = "https://api.twitter.com/";
+
+ public OAuthConfig() {}
+
+ public OAuthConfig(IResourceManager appSettings)
+ {
+ OAuthRealm = appSettings.Get("OAuthRealm", OAuthRealm);
+
+ this.CallbackUrl = appSettings.GetString("CallbackUrl");
+ this.ConsumerKey = appSettings.GetString("ConsumerKey");
+ this.ConsumerSecret = appSettings.GetString("ConsumerSecret");
+ this.RequestTokenUrl = appSettings.Get("RequestTokenUrl", OAuthRealm + "oauth/request_token");
+ this.AuthorizeUrl = appSettings.Get("AuthorizeUrl", OAuthRealm + "oauth/authorize");
+ this.AccessTokenUrl = appSettings.Get("AccessTokenUrl", OAuthRealm + "oauth/access_token");
+ }
+
+ public string CallbackUrl { get; set; }
+ public string ConsumerKey { get; set; }
+ public string ConsumerSecret { get; set; }
+ public string RequestTokenUrl { get; set; }
+ public string AuthorizeUrl { get; set; }
+ public string AccessTokenUrl { get; set; }
+
+ }
+}
+
View
122 src/ServiceStack.ServiceInterface/OAuth/OAuthService.cs
@@ -0,0 +1,122 @@
+using System;
+using System.Net;
+using ServiceStack.Common;
+using ServiceStack.Common.Utils;
+using ServiceStack.Common.Web;
+using ServiceStack.ServiceHost;
+using ServiceStack.ServiceInterface.ServiceModel;
+using ServiceStack.Text;
+using ServiceStack.WebHost.Endpoints;
+
+namespace ServiceStack.ServiceInterface.OAuth
+{
+ public class OAuth
+ {
+ public string State { get; set; }
+ public string oauth_token { get; set; }
+ public string oauth_verifier { get; set; }
+ }
+
+ public class OAuthResponse
+ {
+ public OAuthResponse()
+ {
+ this.ResponseStatus = new ResponseStatus();
+ }
+
+ public ResponseStatus ResponseStatus { get; set; }
+ }
+
+ public class OAuthService : RestServiceBase<OAuth>
+ {
+ public static OAuthConfig OAuthConfig { get; private set; }
+ public static Func<IOAuthSession> SessionFactory { get; private set; }
+
+ public static string GetSessionKey(string sessionId)
+ {
+ return IdUtils.CreateUrn<IOAuthSession>(sessionId);
+ }
+
+ public static void Register(IAppHost appHost, OAuthConfig config, Func<IOAuthSession> sessionFactory)
+ {
+ OAuthConfig = config;
+ SessionFactory = sessionFactory;
+ appHost.RegisterService<OAuthService>();
+
+ SessionFeature.Register(appHost);
+
+ appHost.RequestFilters.Add((req, res, dto) => {
+ var requiresAuth = dto.GetType().GetCustomAttributes(typeof(AuthenticateAttribute), true).Length > 0;
+ if (requiresAuth)
+ {
+ var sessionId = req.GetItemOrCookie("ss-psession");
+ using (var cache = appHost.GetCacheClient())
+ {
+ var session = sessionId != null ? cache.GetSession(sessionId) : null;
+ if (session == null || !session.IsAuthorized())
+ {
+ res.StatusCode = (int)HttpStatusCode.Unauthorized;
+ res.AddHeader(HttpHeaders.WwwAuthenticate, "OAuth realm=\"{0}\"".Fmt(OAuthConfig.OAuthRealm));
+ res.Close();
+ return;
+ }
+ }
+ }
+ });
+ }
+
+ public override object OnGet(OAuth request)
+ {
+ var session = this.GetSession();
+
+ if (session.ReferrerUrl.IsNullOrEmpty())
+ {
+ session.ReferrerUrl = base.RequestContext.GetHeader("Referer") ?? OAuthConfig.CallbackUrl;
+ }
+
+ var oAuth = new OAuthAuthorizer(OAuthConfig);
+
+ if (!session.IsAuthorized())
+ {
+ if (!session.RequestToken.IsNullOrEmpty() && !request.oauth_token.IsNullOrEmpty())
+ {
+ oAuth.RequestToken = session.RequestToken;
+ oAuth.RequestTokenSecret = session.RequestTokenSecret;
+ oAuth.AuthorizationToken = request.oauth_token;
+ oAuth.AuthorizationVerifier = request.oauth_verifier;
+
+ if (oAuth.AcquireAccessToken())
+ {
+ session.OAuthToken = oAuth.AccessToken;
+ session.AccessToken = oAuth.AccessTokenSecret;
+ session.OnAuthenticated(this, oAuth.AuthInfo);
+ this.SaveSession(session);
+
+ //Haz access!
+ return this.Redirect(session.ReferrerUrl.AddQueryParam("s", "1"));
+ }
+
+ //No Joy :(
+ return this.Redirect(session.ReferrerUrl.AddQueryParam("f", "AccessTokenFailed"));
+ }
+ if (oAuth.AcquireRequestToken())
+ {
+ session.RequestToken = oAuth.RequestToken;
+ session.RequestTokenSecret = oAuth.RequestTokenSecret;
+ this.SaveSession(session);
+
+ //Redirect to OAuth provider to approve access
+ return this.Redirect(OAuthConfig.AuthorizeUrl
+ .AddQueryParam("oauth_token", session.RequestToken)
+ .AddQueryParam("oauth_callback", session.ReferrerUrl));
+ }
+
+ return this.Redirect(session.ReferrerUrl.AddQueryParam("f", "RequestTokenFailed"));
+ }
+
+ //Already Authenticated
+ return this.Redirect(session.ReferrerUrl.AddQueryParam("s", "0"));
+ }
+ }
+}
+
View
58 src/ServiceStack.ServiceInterface/OAuth/OAuthUserSession.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using ServiceStack.Common;
+
+namespace ServiceStack.ServiceInterface.OAuth
+{
+ public class OAuthUserSession : IOAuthSession
+ {
+ public OAuthUserSession()
+ {
+ this.Items = new Dictionary<string, string>();
+ }
+
+ public string ReferrerUrl { get; set; }
+
+ public string Id { get; set; }
+
+ public string TwitterUserId { get; set; }
+
+ public string TwitterScreenName { get; set; }
+
+ public string OAuthToken { get; set; }
+
+ public string AccessToken { get; set; }
+
+ public string RequestToken { get; set; }
+
+ public string RequestTokenSecret { get; set; }
+
+ public DateTime CreatedAt { get; set; }
+
+ public DateTime LastModified { get; set; }
+
+ public Dictionary<string, string> Items { get; set; }
+
+ public virtual bool IsAuthorized()
+ {
+ return !string.IsNullOrEmpty(OAuthToken)
+ && !string.IsNullOrEmpty(AccessToken);
+ }
+
+ public virtual void OnAuthenticated(OAuthService oAuthService, Dictionary<string, string> authInfo)
+ {
+ if (authInfo.ContainsKey("user_id"))
+ {
+ this.TwitterUserId = authInfo.GetValueOrDefault("user_id");
+ authInfo.Remove("user_info");
+ }
+ if (authInfo.ContainsKey("screen_name"))
+ {
+ this.TwitterScreenName = authInfo.GetValueOrDefault("screen_name");
+ authInfo.Remove("screen_name");
+ }
+ authInfo.ForEach((x, y) => this.Items[x] = y);
+ }
+ }
+
+}
View
5 src/ServiceStack.ServiceInterface/ServiceBase.cs
@@ -13,7 +13,6 @@
namespace ServiceStack.ServiceInterface
{
-
/// <summary>
/// A Useful ServiceBase for all services with support for automatically serializing
/// Exceptions into a common ResponseError DTO so errors can be handled generically by clients.
@@ -23,7 +22,7 @@ namespace ServiceStack.ServiceInterface
/// </summary>
/// <typeparam name="TRequest"></typeparam>
public abstract class ServiceBase<TRequest>
- : IService<TRequest>, IRequiresRequestContext
+ : IService<TRequest>, IRequiresRequestContext, IServiceBase
{
private static readonly ILog Log = LogManager.GetLogger(typeof(ServiceBase<>));
@@ -114,7 +113,7 @@ public virtual string GetRequestErrorBody()
return string.Format("[{0}: {1}]:\n[REQUEST: {2}]", GetType().Name, DateTime.UtcNow, requestString);
}
- protected T TryResolve<T>()
+ public T TryResolve<T>()
{
return this.AppHost == null
? default(T)
View
106 src/ServiceStack.ServiceInterface/ServiceExtensions.cs
@@ -0,0 +1,106 @@
+using System;
+using System.Net;
+using ServiceStack.CacheAccess;
+using ServiceStack.CacheAccess.Providers;
+using ServiceStack.Common.Web;
+using ServiceStack.Redis;
+using ServiceStack.ServiceHost;
+using ServiceStack.ServiceInterface.OAuth;
+using ServiceStack.Text;
+using ServiceStack.WebHost.Endpoints;
+
+namespace ServiceStack.ServiceInterface
+{
+ public static class ServiceExtensions
+ {
+ public static string AddQueryParam(this string url, string key, string val)
+ {
+ var prefix = url.IndexOf('?') == -1 ? "?" : "&";
+ return url + prefix + key + "=" + val.UrlEncode();
+ }
+
+ public static IHttpResult Redirect(this IServiceBase service, string url)
+ {
+ return service.Redirect(url, "Moved Temporarily");
+ }
+
+ public static IHttpResult Redirect(this IServiceBase service, string url, string message)
+ {
+ return new HttpResult(HttpStatusCode.Redirect, message) {
+ ContentType = service.RequestContext.ResponseContentType,
+ Headers = {
+ { HttpHeaders.Location, url }
+ },
+ };
+ }
+
+ public static IHttpResult AuthenticationRequired(this IServiceBase service)
+ {
+ return new HttpResult {
+ StatusCode = HttpStatusCode.Unauthorized,
+ ContentType = service.RequestContext.ResponseContentType,
+ Headers = {
+ { HttpHeaders.WwwAuthenticate, "OAuth realm=\"{0}\"".Fmt(OAuthConfig.OAuthRealm) }
+ },
+ };
+ }
+
+ public static string GetSessionId(this IServiceBase service)
+ {
+ var req = service.RequestContext.Get<IHttpRequest>();
+ var id = req.GetItemOrCookie("ss-psession");
+ if (id == null)
+ throw new ArgumentNullException("Session not set. Is Session being set in RequestFilters?");
+
+ return id;
+ }
+
+ /// <summary>
+ /// If they don't have an ICacheClient configured use an In Memory one.
+ /// </summary>
+ private static readonly MemoryCacheClient DefaultCache = new MemoryCacheClient { DontDispose = true };
+
+ public static ICacheClient GetCacheClient(this IServiceBase service)
+ {
+ return service.TryResolve<ICacheClient>()
+ ?? (ICacheClient)service.TryResolve<IRedisClientsManager>()
+ ?? DefaultCache;
+ }
+
+ public static ICacheClient GetCacheClient(this IAppHost appHost)
+ {
+ return appHost.TryResolve<ICacheClient>()
+ ?? (ICacheClient)appHost.TryResolve<IRedisClientsManager>()
+ ?? DefaultCache;
+ }
+
+ public static void SaveSession(this IServiceBase service, IOAuthSession session)
+ {
+ using (var cache = service.GetCacheClient())
+ {
+ var sessionKey = OAuthService.GetSessionKey(service.GetSessionId());
+ cache.Set(sessionKey, session);
+ }
+ }
+
+ public static IOAuthSession GetSession(this IServiceBase service)
+ {
+ using (var cache = service.GetCacheClient())
+ {
+ return GetSession(cache, service.GetSessionId());
+ }
+ }
+
+ public static IOAuthSession GetSession(this ICacheClient cache, string sessionId)
+ {
+ var session = cache.Get<IOAuthSession>(OAuthService.GetSessionKey(sessionId));
+ if (session == null)
+ {
+ session = OAuthService.SessionFactory();
+ session.Id = sessionId;
+ session.CreatedAt = session.LastModified = DateTime.UtcNow;
+ }
+ return session;
+ }
+ }
+}
View
19 src/ServiceStack.ServiceInterface/ServiceStack.ServiceInterface.csproj
@@ -99,17 +99,26 @@
</ItemGroup>
<ItemGroup>
<Compile Include="AsyncServiceBase.cs" />
+ <Compile Include="AuthenticateAttribute.cs" />
<Compile Include="Config.cs" />
+ <Compile Include="IServiceBase.cs" />
+ <Compile Include="OAuth\IOAuthSession.cs" />
+ <Compile Include="OAuth\OAuthAuthorizer.cs" />
+ <Compile Include="OAuth\OAuthConfig.cs" />
+ <Compile Include="OAuth\OAuthService.cs" />
+ <Compile Include="OAuth\OAuthUserSession.cs" />
+ <Compile Include="ServiceExtensions.cs" />
<Compile Include="RestServiceBase.cs" />
<Compile Include="ServiceBase.cs" />
<Compile Include="ServiceModel\ResponseStatusTranslator.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ServiceResponseException.cs" />
- <Compile Include="Session\CachedUserSessionManager.cs" />
- <Compile Include="Session\IUserSessionManager.cs" />
- <Compile Include="Session\UserClientSession.cs" />
- <Compile Include="Session\PublicAndPrivateClientSessions.cs" />
- <Compile Include="Session\UserSession.cs" />
+ <Compile Include="Deprecated.Session\CachedUserSessionManager.cs" />
+ <Compile Include="Deprecated.Session\IUserSessionManager.cs" />
+ <Compile Include="Deprecated.Session\UserClientSession.cs" />
+ <Compile Include="Deprecated.Session\PublicAndPrivateClientSessions.cs" />
+ <Compile Include="Deprecated.Session\UserSession.cs" />
+ <Compile Include="SessionFeature.cs" />
<Compile Include="Testing\BasicAppHost.cs" />
<Compile Include="Testing\MockHttpRequest.cs">
<SubType>Code</SubType>
View
33 src/ServiceStack.ServiceInterface/SessionFeature.cs
@@ -0,0 +1,33 @@
+using System;
+using ServiceStack.ServiceHost;
+using ServiceStack.WebHost.Endpoints;
+
+namespace ServiceStack.ServiceInterface
+{
+ public class SessionFeature
+ {
+ private static bool alreadyConfigured;
+
+ public static void Register(IAppHost appHost)
+ {
+ if (alreadyConfigured) return;
+ alreadyConfigured = true;
+
+ //Add permanent and session cookies if not already set.
+ appHost.RequestFilters.Add((req, res, dto) => {
+ if (req.GetCookieValue("ss-session") == null)
+ {
+ var sessionId = Guid.NewGuid().ToString("N");
+ res.SetSessionCookie("ss-session", sessionId);
+ req.Items["ss-session"] = sessionId;
+ }
+ if (req.GetCookieValue("ss-psession") == null)
+ {
+ var permanentId = Guid.NewGuid().ToString("N");
+ res.SetPermanentCookie("ss-psession", permanentId);
+ req.Items["ss-psession"] = permanentId;
+ }
+ });
+ }
+ }
+}
View
5 src/ServiceStack.ServiceInterface/Testing/BasicAppHost.cs
@@ -35,5 +35,10 @@ public T TryResolve<T>()
public List<HttpHandlerResolverDelegate> CatchAllHandlers { get; set; }
public EndpointHostConfig Config { get; set; }
+
+ public void RegisterService(Type serviceType)
+ {
+ Config.ServiceManager.RegisterService(serviceType);
+ }
}
}
View
5 src/ServiceStack.ServiceInterface/Testing/TestAppHost.cs
@@ -50,5 +50,10 @@ public T TryResolve<T>()
public List<HttpHandlerResolverDelegate> CatchAllHandlers { get; private set; }
public EndpointHostConfig Config { get; set; }
+
+ public void RegisterService(Type serviceType)
+ {
+ Config.ServiceManager.RegisterService(serviceType);
+ }
}
}
View
12 src/ServiceStack/AppHostExtensions.cs
@@ -0,0 +1,12 @@
+using ServiceStack.WebHost.Endpoints;
+
+namespace ServiceStack
+{
+ public static class AppHostExtensions
+ {
+ public static void RegisterService<TService>(this IAppHost appHost)
+ {
+ appHost.RegisterService(typeof(TService));
+ }
+ }
+}
View
4 src/ServiceStack/CacheAccess.Providers/MemoryCacheClient.cs
@@ -12,6 +12,8 @@ public class MemoryCacheClient
private Dictionary<string, CacheEntry> memory;
private Dictionary<string, int> counters;
+ public bool DontDispose { get; set; }
+
private class CacheEntry
{
private object cacheValue;
@@ -116,6 +118,8 @@ private bool CacheReplace(string key, object value, DateTime expiresAt)
public void Dispose()
{
+ if (DontDispose) return;
+
this.memory = new Dictionary<string, CacheEntry>();
this.counters = new Dictionary<string, int>();
}
View
30 src/ServiceStack/Configuration/ConfigurationResourceManager.cs
@@ -29,4 +29,34 @@ public T Get<T>(string name, T defaultValue)
: defaultValue;
}
}
+
+ /// <summary>
+ /// More familiar name for the new crowd.
+ /// </summary>
+ public class AppSettings : IResourceManager
+ {
+ public string GetString(string name)
+ {
+ return ConfigUtils.GetNullableAppSetting(name);
+ }
+
+ public IList<string> GetList(string key)
+ {
+ return ConfigUtils.GetListFromAppSetting(key);
+ }
+
+ public IDictionary<string, string> GetDictionary(string key)
+ {
+ return ConfigUtils.GetDictionaryFromAppSetting(key);
+ }
+
+ public T Get<T>(string name, T defaultValue)
+ {
+ var stringValue = ConfigUtils.GetNullableAppSetting(name);
+
+ return stringValue != null
+ ? TypeSerializer.DeserializeFromString<T>(stringValue)
+ : defaultValue;
+ }
+ }
}
View
49 src/ServiceStack/ServiceHost/ServiceController.cs
@@ -69,37 +69,42 @@ public void Register(ITypeFactory serviceFactoryFn)
{
foreach (var serviceType in ResolveServicesFn())
{
- if (serviceType.IsAbstract || serviceType.ContainsGenericParameters) continue;
+ RegisterService(serviceFactoryFn, serviceType);
+ }
+ }
- foreach (var service in serviceType.GetInterfaces())
- {
- if (!service.IsGenericType
- || service.GetGenericTypeDefinition() != typeof(IService<>)
- ) continue;
+ public void RegisterService(ITypeFactory serviceFactoryFn, Type serviceType)
+ {
+ if (serviceType.IsAbstract || serviceType.ContainsGenericParameters) return;
- var requestType = service.GetGenericArguments()[0];
+ foreach (var service in serviceType.GetInterfaces())
+ {
+ if (!service.IsGenericType
+ || service.GetGenericTypeDefinition() != typeof (IService<>)
+ ) continue;
- Register(requestType, serviceType, serviceFactoryFn);
+ var requestType = service.GetGenericArguments()[0];
- RegisterRestPaths(requestType);
+ Register(requestType, serviceType, serviceFactoryFn);
- this.ServiceTypes.Add(serviceType);
+ RegisterRestPaths(requestType);
- this.AllOperationTypes.Add(requestType);
- this.OperationTypes.Add(requestType);
+ this.ServiceTypes.Add(serviceType);
- var responseTypeName = requestType.FullName + ResponseDtoSuffix;
- var responseType = AssemblyUtils.FindType(responseTypeName);
- if (responseType != null)
- {
- this.AllOperationTypes.Add(responseType);
- this.OperationTypes.Add(responseType);
- }
+ this.AllOperationTypes.Add(requestType);
+ this.OperationTypes.Add(requestType);
- Log.DebugFormat("Registering {0} service '{1}' with request '{2}'",
- (responseType != null ? "SyncReply" : "OneWay"),
- serviceType.Name, requestType.Name);
+ var responseTypeName = requestType.FullName + ResponseDtoSuffix;
+ var responseType = AssemblyUtils.FindType(responseTypeName);
+ if (responseType != null)
+ {
+ this.AllOperationTypes.Add(responseType);
+ this.OperationTypes.Add(responseType);
}
+
+ Log.DebugFormat("Registering {0} service '{1}' with request '{2}'",
+ (responseType != null ? "SyncReply" : "OneWay"),
+ serviceType.Name, requestType.Name);
}
}
View
24 src/ServiceStack/ServiceHost/ServiceManager.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
+using ServiceStack.Configuration;
using ServiceStack.Logging;
using Funq;
@@ -79,16 +80,35 @@ public ServiceManager(Container container, ServiceController serviceController)
this.ServiceController = serviceController;
}
+ private ExpressionTypeFunqContainer typeFactory;
+
public void Init()
{
- var typeFactory = new ExpressionTypeFunqContainer(this.Container);
+ typeFactory = new ExpressionTypeFunqContainer(this.Container);
this.ServiceController.Register(typeFactory);
+ ReloadServiceOperations();
+
+ typeFactory.RegisterTypes(this.ServiceController.ServiceTypes);
+ }
+
+ public void ReloadServiceOperations()
+ {
this.ServiceOperations = new ServiceOperations(this.ServiceController.OperationTypes);
this.AllServiceOperations = new ServiceOperations(this.ServiceController.AllOperationTypes);
+ }
- typeFactory.RegisterTypes(this.ServiceController.ServiceTypes);
+ public void RegisterService<T>()
+ {
+ this.ServiceController.RegisterService(typeFactory, typeof(T));
+ typeFactory.Register<T>();
+ }
+
+ public void RegisterService(Type serviceType)
+ {
+ this.ServiceController.RegisterService(typeFactory, serviceType);
+ typeFactory.RegisterTypes(serviceType);
}
public object Execute(object dto)
View
1  src/ServiceStack/ServiceStack.csproj
@@ -120,6 +120,7 @@
<Compile Include="Funq\ServiceEntry.Generic.cs" />
<Compile Include="Funq\ServiceKey.cs" />
<Compile Include="Funq\Syntax.cs" />
+ <Compile Include="AppHostExtensions.cs" />
<Compile Include="Markdown\CachedExpressionCompiler.cs" />
<Compile Include="Markdown\DynamicTypeGenerator .cs" />
<Compile Include="Markdown\ExpressionHelper.cs" />
View
19 src/ServiceStack/WebHost.EndPoints/AppHostBase.cs
@@ -76,16 +76,20 @@ public void Init()
{
serviceManager.Init();
Configure(EndpointHost.Config.ServiceManager.Container);
-
- EndpointHost.SetOperationTypes(
- serviceManager.ServiceOperations,
- serviceManager.AllServiceOperations
- );
}
else
{
Configure(null);
}
+ if (serviceManager != null)
+ {
+ //Required for adhoc services added in Configure()
+ serviceManager.ReloadServiceOperations();
+ EndpointHost.SetOperationTypes(
+ serviceManager.ServiceOperations,
+ serviceManager.AllServiceOperations
+ );
+ }
EndpointHost.AfterInit();
@@ -166,6 +170,11 @@ public EndpointHostConfig Config
get { return EndpointHost.Config; }
}
+ public void RegisterService(Type serviceType)
+ {
+ EndpointHost.Config.ServiceManager.RegisterService(serviceType);
+ }
+
public virtual void Dispose()
{
if (EndpointHost.Config.ServiceManager != null)
View
2  src/ServiceStack/WebHost.EndPoints/IAppHost.cs
@@ -19,5 +19,7 @@ public interface IAppHost
List<HttpHandlerResolverDelegate> CatchAllHandlers { get; }
EndpointHostConfig Config { get; }
+
+ void RegisterService(Type serviceType);
}
}
View
19 src/ServiceStack/WebHost.EndPoints/Support/HttpListenerBase.cs
@@ -71,16 +71,20 @@ public void Init()
{
serviceManager.Init();
Configure(EndpointHost.Config.ServiceManager.Container);
-
- EndpointHost.SetOperationTypes(
- serviceManager.ServiceOperations,
- serviceManager.AllServiceOperations
- );
}
else
{
Configure(null);
}
+ if (serviceManager != null)
+ {
+ //Required for adhoc services added in Configure()
+ serviceManager.ReloadServiceOperations();
+ EndpointHost.SetOperationTypes(
+ serviceManager.ServiceOperations,
+ serviceManager.AllServiceOperations
+ );
+ }
EndpointHost.AfterInit();
@@ -329,6 +333,11 @@ public EndpointHostConfig Config
get { return EndpointHost.Config; }
}
+ public void RegisterService(Type serviceType)
+ {
+ EndpointHost.Config.ServiceManager.RegisterService(serviceType);
+ }
+
public virtual void Dispose()
{
this.Stop();
View
8 tests/ServiceStack.Common.Tests/MessagingTests.cs
@@ -1,6 +1,7 @@
using System;
using NUnit.Framework;
using ServiceStack.Messaging;
+using ServiceStack.ServiceInterface.OAuth;
using ServiceStack.Text;
namespace ServiceStack.Common.Tests
@@ -46,5 +47,12 @@ public void Can_serialize_IMessage_ToBytes_into_typed_Message()
Assert.That(typedMessage.GetBody().Value, Is.EqualTo(dto.Value));
}
+ [Test]
+ public void Can_deserialize_concrete_type_into_IOAuthSession()
+ {
+ var json = "{\"__type\":\"ChaweetApi.ServiceInterface.UserSession, ChaweetApi.ServiceInterface\",\"ReferrerUrl\":\"http://localhost:4629/oauth\",\"Id\":\"0412cc4654484111b2e7162a24a83753\",\"RequestToken\":\"dw4U1RUBr8r5Bx1oBZfdmNiocsMrAtBmSoFHYCZrr4\",\"RequestTokenSecret\":\"HNvCiD1a61CrutnxZoiJXQlLKNN1GAtWn7pRuafYN0\",\"CreatedAt\":\"\\/Date(1320221243138+0000)\\/\",\"LastModified\":\"\\/Date(1320221243138+0000)\\/\",\"Items\":{}}";
+ var fromJson = json.FromJson<IOAuthSession>();
+ Assert.That(fromJson, Is.Not.Null);
+ }
}
}
View
4 tests/ServiceStack.Common.Tests/ServiceStack.Common.Tests.csproj
@@ -176,6 +176,10 @@
<Project>{42E1C8C0-A163-44CC-92B1-8F416F2C0B01}</Project>
<Name>ServiceStack.Interfaces</Name>
</ProjectReference>
+ <ProjectReference Include="..\..\src\ServiceStack.ServiceInterface\ServiceStack.ServiceInterface.csproj">
+ <Project>{5A315F92-80D2-4C60-A5A4-22E027AC7E7E}</Project>
+ <Name>ServiceStack.ServiceInterface</Name>
+ </ProjectReference>
</ItemGroup>
<ItemGroup>
<Content Include="sqlite3.dll">
View
5 tests/ServiceStack.ServiceHost.Tests/Formats/ViewTests.cs
@@ -68,6 +68,11 @@ public T TryResolve<T>()
public List<HttpHandlerResolverDelegate> CatchAllHandlers { get; set; }
public EndpointHostConfig Config { get; set; }
+
+ public void RegisterService(Type serviceType)
+ {
+ Config.ServiceManager.RegisterService(serviceType);
+ }
}
public string GetHtml(object dto, string format)

0 comments on commit 8fb8cc6

Please sign in to comment.
Something went wrong with that request. Please try again.