I'm getting along quite nicely with ServiceStack, but ran into an issue which I can't currently work round. In my Global.asax.cs Configure() method, I declare a database based AppSettings as follows:
// Create app settings, based on dictionary settings from central database
Dictionary<string, string> configSettings = null;
using (var db = container.Resolve<IDbConnectionFactory>().Open())
{
configSettings = db.Dictionary<string, string>(db.From<ConfigSetting>());
}
var dicSettings = new DictionarySettings(configSettings);
// Register app settings for injection
container.Register<IAppSettings>(dicSettings);
This works great in code in which the AppSettings is injected:
public class RemoteEmailValidator : IEmailValidator
{
public IAppSettings AppSettings { get; set; }
public bool ValidEmail(string email)
{
try
{
if (String.IsNullOrEmpty(email) || email.Length > 256)
{
return false;
}
RestClient client = new RestClient();
string baseURL = AppSettings.Get("MailgunAPIBaseURL", "");
if (String.IsNullOrEmpty(baseURL))
{
// If we can't check email in-depth, then it's OK
return true;
}
else
{
client.BaseUrl = new Uri(baseURL);
client.Authenticator =
new HttpBasicAuthenticator(AppSettings.Get("MailgunAPIUserName", ""),
AppSettings.Get("MailgunAPIPublicKey", ""));
RestRequest request = new RestRequest();
request.Resource = AppSettings.Get("MailgunAPIEmailValidationResource", "");
request.AddParameter("address", email);
var response = client.Execute(request);
dynamic content = DynamicJson.Deserialize(response.Content);
return content.is_valid == "true";
}
}
catch { } // Suppress any errors, because email validation is nice to have
return true;
}
}
The values are retrieved from the database as you'd expect. However, when I try accessing AppSettings from a view, the values are returned from the web.config. E.g. in my register.cshtml:
string maxHouseholdMembers = HostContext.AppSettings.Get("MaxMembersPerHousehold", "20");
the value wasn't varying based on the value from the database, but was varying based on updating web.config. So, is there any additional config I need to do, to get my view to use the custom AppSettings I created in the global.asax.cs Configure() method?
Found the answer while I was actually posting the question. Just needed to add an extra line in the Configure() method:
// Create app settings, based on dictionary settings from central database
Dictionary<string, string> configSettings = null;
using (var db = container.Resolve<IDbConnectionFactory>().Open())
{
configSettings = db.Dictionary<string, string>(db.From<ConfigSetting>());
}
var dicSettings = new DictionarySettings(configSettings);
AppSettings = dicSettings;
The final line (AppSettings = dicSettings) does the trick and overrides the standard AppSettings.
Related
I have added a Connected Reference in Visual Studio 2019. It consumed a https endpoint, and created all binding information needed into a reference.cs file.
It didn't generate any App.config file, so I suspected what I needed was bundled into the reference.cs file. Indeed, looking into it, it mostly was.
So I tried creating a client, specify client credentials in two ways, as you can see, but still, doesn't matter how I specify it, I get an exception when calling this code below.
public async Task SendFile(Stream fileStream, string fileName, Guid machineKey)
{
_logger.LogInformation("Starting file sending to Manager 1.");
_logger.LogInformation($"Sending file {fileName} from Machine {machineKey}");
try
{
var client = new FileTransferClient(FileTransferClient.EndpointConfiguration.BasicHttpBinding_IFileTransfer, _options.FileTransferEndPoint)
{
ClientCredentials =
{
UserName =
{
UserName = _options.FileTransferUsername,
Password = _options.FileTransferPassword
}
}
};
client.ClientCredentials.UserName.UserName = _options.FileTransferUsername;
client.ClientCredentials.UserName.Password = _options.FileTransferPassword;
using (new OperationContextScope(client.InnerChannel))
{
}
await client.UploadAsync(new FileUploadMessage
{
// Assume that this is enough. Can't really supply file length...
FileInfo = new FileTransferInfo
{
TransferId = new Guid(),
MachineUUID = machineKey.ToString(),
Name = fileName
},
TransferStream = fileStream
});
}
catch (Exception e)
{
_logger.LogError("An unexpected exception occurred while sending file to Manager 1G.", e);
}
_logger.LogInformation("File sending finished.");
}
The exception is "The HTTP request is unauthorized with client authentication scheme 'Basic'. The authentication header received from the server was 'Basic Realm'."
I have compared to similar APIs that use the beforementioned App.config, and have edited the reference.cs to match the security I think it should have.
Specifically, I've added the security related lines here:
private static System.ServiceModel.Channels.Binding GetBindingForEndpoint(EndpointConfiguration endpointConfiguration)
{
if ((endpointConfiguration == EndpointConfiguration.BasicHttpBinding_IFileTransfer))
{
System.ServiceModel.BasicHttpBinding result = new System.ServiceModel.BasicHttpBinding();
result.MaxBufferSize = int.MaxValue;
result.ReaderQuotas = System.Xml.XmlDictionaryReaderQuotas.Max;
result.MaxReceivedMessageSize = int.MaxValue;
result.AllowCookies = true;
result.Security.Mode = System.ServiceModel.BasicHttpSecurityMode.Transport;
result.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
result.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;
return result;
}
if ((endpointConfiguration == EndpointConfiguration.MetadataExchangeHttpsBinding_IFileTransfer))
{
System.ServiceModel.Channels.CustomBinding result = new System.ServiceModel.Channels.CustomBinding();
System.ServiceModel.Channels.TextMessageEncodingBindingElement textBindingElement = new System.ServiceModel.Channels.TextMessageEncodingBindingElement();
result.Elements.Add(textBindingElement);
System.ServiceModel.Channels.HttpsTransportBindingElement httpsBindingElement = new System.ServiceModel.Channels.HttpsTransportBindingElement();
httpsBindingElement.AllowCookies = true;
httpsBindingElement.MaxBufferSize = int.MaxValue;
httpsBindingElement.MaxReceivedMessageSize = int.MaxValue;
result.Elements.Add(httpsBindingElement);
return result;
}
throw new System.InvalidOperationException(string.Format("Could not find endpoint with name \'{0}\'.", endpointConfiguration));
}
What I found dumbfounding, was that with embedding in the constructor calling setting the ClientCredentials, they were not in any way populated when I inspected the client with a debug session attached. Hence I tried to set it afterwards specifically.
But either way, the end result is the same, get the same error.
How can I resolve that error in Code?
I can in theory try to add an App.config and do it there, but I don't know the Contract. And I am not sure what to look for in the generated reference.cs to identify it. So I'd prefer to learn to do this by Code, as the Contract is already in place there, and I can supply the endpoint via the _options, so it should be able to configure for different environments by that.
Turned out I had indeed password and username exchanged, so fixing that helped me get past of this issue.
I am a total noob in C# and tried to add an automated mail services to my backend API for an Angular FrontEnd.
It works properly as intended for one time, but cannot be used a second time. I guess I am violating some object rules, maybe someone is able to point out my mistake.
This is an excerpt of my file UserAuthController.cs which includes the register function. When registration on my website is successful it shall also call the API Service from my automated mail system.
I didn't know how to include the function properly so I've added it with a new namespace.
namespace Gogo_Api.Controllers
{
[RoutePrefix("UserAuth")]
[EnableCors(origins: "*", headers: "*", methods: "*")]
public class UserAuthController : ApiController
{
private readonly IUserAuthManager _userAuthManager;
public UserAuthController()
{
}
public UserAuthController(IUserAuthManager userAuthManager)
{
this._userAuthManager = userAuthManager;
}
[HttpPost]
[Route("Register")]
public HttpResponseMessage Register(UserDetails user)
{
var response = new RegisterResponse();
response.code = _userAuthManager.Register(user);
if (response.code == 1)
{
response.message = "Registration successful ";
//Including API Service here...
Sendinblue.Program RegisterMail = new Sendinblue.Program();
RegisterMail.Main(user.email, user.displayName, user.country);
RegisterMail = null;
}
else if (response.code == 2)
{
response.message = "User already registered ";
}
else if (response.code == 0)
{
response.message = "Error occured";
}
return Request.CreateResponse(HttpStatusCode.OK, response);
}
}
}
namespace Sendinblue
{
class Program
{
public void Main(string userMail, string userName, string userCountry)
{
Configuration.Default.ApiKey.Add("api-key", "MYKEY");
var apiInstance = new ContactsApi();
string email = userMail;
JObject attributes = new JObject();
attributes.Add("USERNAME", userName);
attributes.Add("COUNTRY", userCountry);
List<long?> listIds = new List<long?>();
listIds.Add(5);
try
{
var createContact = new CreateContact(email, attributes, emailBlacklisted, smsBlacklisted, listIds, updateEnabled, smtpBlacklistSender);
CreateUpdateContactModel result = apiInstance.CreateContact(createContact);
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
Console.WriteLine(e.Message);
}
}
}
}
I have added 'RegisterMail = null;' because I thought I need to delete my object first before using it again, but still it works only for the first time.
How would I be able to call my function multiple times?
Thanks
Thanks #Richard Barker, your comment helped me fix it.
I have moved everything out of the Controller and had to move
Configuration.Default.ApiKey.Add...
apiInstance = new ContactsApi();
to a one-time call in the Initializer, so the API Call and ContactsApi is only created once, instead of everytime.
I am new to the Service Stack library and trying to use the Server Events Client. The server I'm working with has two URIs. One for receiving a connection token and one for listening for search requests using the token acquired in the previous call.
I use a regular JsonServiceClient with digest authentication to get the token like so:
public const string Baseurl = "http://serverIp:port";
var client = new JsonServiceClient(Baseurl)
{
UserName = "user",
Password = "password",
AlwaysSendBasicAuthHeader = false
};
//ConnectionData has a string token property
var connectionData = client.Get<ConnectionData>("someServices/connectToSomeService");
And then use this token to listen for server events. Like so:
var eventClient =
new ServerEventsClient($"{Baseurl}/differentUri/retrieveSearchRequests?token={connectionData.Token}")
{
OnConnect = Console.WriteLine,
OnMessage = message => Console.WriteLine(message.Json),
OnCommand = message => Console.WriteLine(message.Json),
OnException = WriteLine,
ServiceClient = client, //same JsonServiceClient from the previous snippet
EventStreamRequestFilter = request =>
{
request.PreAuthenticate = true;
request.Credentials = new CredentialCache
{
{
new Uri(Baseurl), "Digest", new NetworkCredential("user", "password")
}
};
}
};
Console.WriteLine(eventClient.EventStreamUri); // "/event-stream&channels=" is appended at the end
eventClient.Start();
The problem with the above code is that it automatically appends "/event-stream&channels=" at the end of my URI. How do I disable this behavior?
I have tried adding the following class
public class AppHost : AppSelfHostBase
{
public static void Start()
{
new AppHost().Init().Start(Baseurl);
}
public AppHost() : base(typeof(AppHost).Name, typeof(AppHost).Assembly)
{
}
public override void Configure(Container container)
{
Plugins.Add(new ServerEventsFeature
{
StreamPath = string.Empty
});
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[]
{
new DigestAuthProvider()
}));
}
}
and called Start on it, before calling the above code, but still no luck.
The ServerEventsClient is only for listening to ServiceStack SSE Stream and should only be populated with the BaseUrl of the remote ServiceStack instance, i.e. not the path to the /event-stream or a queryString.
See this previous answer for additional customization available, e.g. you can use ResolveStreamUrl to add a QueryString to the EventStream URL it connects to:
var client = new ServerEventsClient(BaseUrl) {
ResolveStreamUrl = url => url.AddQueryParam("token", token)
});
If you've modified ServerEventsFeature.StreamPath to point to a different path, e.g:
Plugins.Add(new ServerEventsFeature
{
StreamPath = "/custom-event-stream"
});
You can change the ServerEventsClient to subscribe to the custom path with:
client.EventStreamPath = client.BaseUri.CombineWith("custom-event-stream");
ResolveStreamUrl + EventStreamPath is available from v5.0.3 that's now available on MyGet.
Having read that since late last year LinkedIn finally have finally allowed us to retrieve the email address for the currently logged on user I've been failing to do so. I've read all the posts I can find on SO and elsewhere and as far as I can tell my code should be working. It returns just fine with all the other fields,
however, the email address field is always empty.
Here's my LinkedInClient class;
public class LinkedInClient2 : OAuthClient
{
public static readonly ServiceProviderDescription LinkedInServiceDescription = new ServiceProviderDescription
{
AccessTokenEndpoint =
new MessageReceivingEndpoint(
"https://api.linkedin.com/uas/oauth/accessToken",
HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
RequestTokenEndpoint =
new MessageReceivingEndpoint(
"https://api.linkedin.com/uas/oauth/requestToken?scope=r_basicprofile+r_emailaddress",
HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
UserAuthorizationEndpoint =
new MessageReceivingEndpoint(
"https://www.linkedin.com/uas/oauth/authenticate",
HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() },
ProtocolVersion = ProtocolVersion.V10a
};
public LinkedInClient2(string consumerKey, string consumerSecret, IConsumerTokenManager tokenManager)
: base("linkedIn", LinkedInServiceDescription, tokenManager)
{
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We don't care if the request fails.")]
protected override AuthenticationResult VerifyAuthenticationCore(AuthorizedTokenResponse response)
{
// See here for Field Selectors API http://developer.linkedin.com/docs/DOC-1014
const string ProfileRequestUrl = "https://api.linkedin.com/v1/people/~:(id,first-name,last-name,email-address,headline,industry,summary,picture-url)";
string accessToken = response.AccessToken;
var profileEndpoint = new MessageReceivingEndpoint(ProfileRequestUrl, HttpDeliveryMethods.GetRequest);
HttpWebRequest request = this.WebWorker.PrepareAuthorizedRequest(profileEndpoint, accessToken);
try
{
using (WebResponse profileResponse = request.GetResponse())
{
using (Stream responseStream = profileResponse.GetResponseStream())
{
XDocument document = LoadXDocumentFromStream(responseStream);
string userId = document.Root.Element("id").Value;
// User Profile Fields - https://developer.linkedin.com/documents/profile-fields
string firstName = document.Root.Element("first-name").Value;
string lastName = document.Root.Element("last-name").Value;
string userName = document.Root.Element("email-address").Value; // <<<<<< ERROR - always empty
var extraData = new Dictionary<string, string>();
extraData.Add("accesstoken", accessToken);
extraData.Add("name", userName);
extraData.AddDataIfNotEmpty(document, "picture-url");
extraData.AddDataIfNotEmpty(document, "location");
extraData.AddDataIfNotEmpty(document, "headline");
extraData.AddDataIfNotEmpty(document, "summary");
extraData.AddDataIfNotEmpty(document, "industry");
return new AuthenticationResult(
isSuccessful: true, provider: this.ProviderName, providerUserId: userId, userName: userName, extraData: extraData);
}
}
}
catch (Exception exception)
{
return new AuthenticationResult(exception);
}
}
internal static XDocument LoadXDocumentFromStream(Stream stream)
{
const int MaxChars = 0x10000; // 64k
XmlReaderSettings settings = new XmlReaderSettings()
{
MaxCharactersInDocument = MaxChars
};
return XDocument.Load(XmlReader.Create(stream, settings));
}
}
}
I realise that I'm supposed to add the scope=r_emailaddress to the RequestTokenEndpoint (which I have) but from the fiddler traces I can't even see that endpoint being fetched. Basically, it only every uses the AccessTokenEndpoint which presumably is something to do with my problem.
This is approximately how my ASP.Net MVC4.5 controller looks;
[AllowAnonymous]
public virtual ActionResult LinkedIn(string returnUrl)
{
var tokenMgr = new RepoOAuthTokenManager(_iOtk, LinkedInAppKey, LinkedInAppSecret);
var iacp = new LinkedInClient2(LinkedInAppKey, LinkedInAppSecret, tokenMgr); // if none specified, LinkedInClient uses the AuthenticationOnlyCookieOAuthTokenManager which doesn't work for APIs
var ioadp = new MyOauthDataProvider();
var oasm = new OpenAuthSecurityManager(this.HttpContext, iacp, ioadp);
var redirectUri = Url.ActionFullyQualified(this.nameof(c => c.LinkedIn(null)), null, new RouteValueDictionary(new { returnUrl = returnUrl }));
AuthenticationResult ar = oasm.VerifyAuthentication(redirectUri);
if (ar.Error == null)
{
if (ar.IsSuccessful)
DoSomethingResultingInRedirect(redirectUri); // OK
else
oasm.RequestAuthentication(redirectUri);
}
else
ModelState.AddModelError("", ar.Error.Message);
return View(this.nameof(c=>c.Login(null)));
}//LinkedIn
I can't say I completely understand the extensibility mechanism in DotNetOpenAuth and I may be misunderstanding something so I'd appreciate some pointers.
Am I missing a step somewhere?
I have two solutions to this, although I still don't understand how to get my existing code to work as I'd expect, but hopefully this may help someone else;
(1) I went to Making it easier for you to add default member permissions and clicked on the API admin page.
Here you can select what scopes you want requested by default. It didn't work until I clicked a box (now disappeared) that was worded along the lines of "[x] Make this permanent". Once I'd done that I started to get the email-address field populated as I was expecting.
(2) I tried using the OAuth2 URL instead from information here and it seemed to work. I have also found an implementation of an OAuth2 client here which looks like a good start. I suspect that in the long run, an OAuth2 upgrade (once the spec is more static) will yield better overall mileage.
For now though, I'm out of the pit of despair, but other answers are still welcome!
I had a similar issue.. maybe this is relevant for you:
My Request Token Call is:
https://api.linkedin.com/v1/people/~:(id,first-name,last-name,headline,member-url-resources,picture-url,location,public-profile-url,email-address)?format=json
but the json response is:
array(8) {
["emailAddress"]=>
string(18) "email#email.com"
["firstName"]=>
string(3) "Tim"
...
Note that in the first case email is named email-address, in the second emailAddress.
I am trying to get the grip on the Facebook SDK and at the same time transitioning from ASP.NET forms to MVC (finally). So please bear with me ..
I have created two controller actions:
FBLogon is execetued when the user clicks on the FB login button on the form.
He is then redirected to the FB login page.
Afterwards he gets sent back to the FBAuthorize page, which is supposed to parse the returned url for the access token. I get something like:
http://localhost:5000/account/FBAuthorize#access_token=199143326771791|827213759889396d5408fee6-100001815992604|BmYchAOMqSoZ2L0TYgCrtpoKP3M&expires_in=0
The problem I see, is that as the access_token is passed behind a #, asp.net cannot parse it on the server. Am I doing something fundamentaly wrong?
Code follows:
public ActionResult FBLogon()
{
var settings = ConfigurationManager.GetSection("facebookSettings");
IFacebookApplication current = null;
if (settings != null)
{
current = settings as IFacebookApplication;
if (current.AppId == "{app id}" || current.AppSecret == "{app secret}")
{
return View();
}
}
string[] extendedPermissions = new[] { "publish_stream", "offline_access" };
var oauth = new FacebookOAuthClient { ClientId = current.AppId, RedirectUri = new Uri("http://localhost:5000/account/FBAuthorize") };
var parameters = new Dictionary<string, object>
{
{ "response_type", "token" },
{ "display", "page" }
};
if (extendedPermissions != null && extendedPermissions.Length > 0)
{
var scope = new StringBuilder();
scope.Append(string.Join(",", extendedPermissions));
parameters["scope"] = scope.ToString();
}
var loginUrl = oauth.GetLoginUrl(parameters);
return Redirect(loginUrl.ToString());
}
public ActionResult FBAuthorize()
{
FacebookOAuthResult result;
if (FacebookOAuthResult.TryParse(Request.Url, out result))
{
if (result.IsSuccess)
{
var accesstoken = result.AccessToken;
}
else
{
var errorDescription = result.ErrorDescription;
var errorReason = result.ErrorReason;
}
}
return View();
}
Ok. The facebook docs say it quite clearly:
Because the access token is passed in
an URI fragment, only client-side code
(such as JavaScript executing in the
browser or desktop code hosting a web
control) can retrieve the token. App
authentication is handled by verifying
that the redirect_uri is in the same
domain as the Site URL configured in
the Developer App
from http://developers.facebook.com/docs/authentication/ ---> Client-side Flow Section.
So I'm sending the token back to my server to complete the authentication..
Update:
The sending back to the server I do using Javascript something like this:
var appId = "<%: Facebook.FacebookContext.Current.AppId %>";
if (window.location.hash.length > 0) {
accessToken = window.location.hash.substring(1);
var url = window.location.href.replace(/#/, '?');
window.location = url;
}
On the server then I have the following action. Not very nice but it works..
public ActionResult FBAuthorize()
{
FacebookOAuthResult result = null;
string url = Request.Url.OriginalString;
/// hack to make FacebookOuthResult accept the token..
url = url.Replace("FBAuthorize?", "FBAuthorize#");
if (FacebookOAuthResult.TryParse(url, out result))
{
if (result.IsSuccess)
{
string[] extendedPermissions = new[] { "user_about_me", "offline_access" };
var fb = new FacebookClient(result.AccessToken);
dynamic resultGet = fb.Get("/me");
var name = resultGet.name;
RegisterModel rm = new Models.RegisterModel();
rm.UserName = name;
rm.Password = "something";
rm.Email = "somethig";
rm.ConfirmPassword = "23213";
//Label1.Text = name;
//Response.Write(name);
//return RedirectToAction("register", "Account", rm);
ViewData["Register"] = rm;
return RedirectToAction("Register");
}
else
{
var errorDescription = result.ErrorDescription;
var errorReason = result.ErrorReason;
}
}
return View();
}
I found this post http://facebooksdk.codeplex.com/discussions/244568 on codeplex. I think this is what you need.
Note that instead of using the client-side flow, you need to use the server-side flow.
This is what you should do
Create a login link for server-side flow. After Authorization, facebook will return an url containing a code instead of a access token.
Then you request for a token from facebook using the code. this is my example
public ActionResult FBAuthorize()
{
FacebookOAuthClient cl = new FacebookOAuthClient(FacebookContext.Current);
FacebookOAuthResult result = null;
string url = Request.Url.OriginalString;
// verify that there is a code in the url
if (FacebookOAuthResult.TryParse(url, out result))
{
if (result.IsSuccess)
{
string code = result.Code;
// this line is necessary till they fix a bug *see details below
cl.RedirectUri = new UriBuilder("http://localhost:5000/account/FBAuthorize").Uri;
var parameters = new Dictionary<string, object>();
//parameters.Add("permissions", "offline_access");
Dictionary<String, Object> dict = (Dictionary<String, Object>)cl.ExchangeCodeForAccessToken(code, new Dictionary<string, object> { { "redirect_uri", "http://localhost:5000/account/FBAuthorize" } });
Object Token = dict.Values.ElementAt(0);
TempData["accessToken"] = Token.ToString();
return RedirectToAction ("ShowUser");
}
else
{
var errorDescription = result.ErrorDescription;
}
}
else
{
// TODO: handle error
}
return View();
}
*There is bug when using IIS in localhost, see the original post for details (the redirect uri when asking for the token must be the same as the one used asking for the code)
It is highly recommended to use IIS and not visual studio web server. There are many things that wont work in visual studio web server.
I am in the same spot you are at the moment.
We never get the Request.QueryString populated becasue of the "fragment" or # in the url.
Love to know if you solved this and how.
It does not look like the FacebookOAuthResult class was written to be used in web applications of any sort.
you can change the response type in you scope paramas to be "code" then it will send back a code in the querystring in which you can swap for a token.