|
There are so many caveats to using a gmail account to just send email.
To the best of my knowledge from what I have learned, if you have a free gmail account, you have to use OAuth2 with clientId and use the browser to authenticate. That's the price of free!
If you have a domain verified Gmail account or Google Cloud account that you pay for; you need service account credentials to use that Gmail account to send email. But with this account type you can access the other services available as well such as Calendar, Drive. $6 a month for each account.
I have the latter paid account, a domain verified Google Cloud account, so I had to create service account credentials, then acquire API permission to the Gmail Service, and then go back to my Google Cloud account and go to security, advanced and set the API permissions to the projectId and service scope for sending email using Gmail. Here you can assign various scopes to the API's you selected to subscribe to. This is the key part or else you get strange errors.
Last I had to write different code and use different form of authentication to get a token and send email.
So I wrote a PFX version to send email, which will be obsolete soon, so now I have to write a Json version to replace it next.
I just tested my prototype code in a production version Docker container on my development machine (Docker for Windows - Linux Container) and it works fine so far, email sent in the background without browser authentication and no token written to the project that I can see. I just tested it in a Linux Docker container running on the production server and the message sent.
It's too bad that the internet is polluted with old documentation and misinformation.
The previous help in this thread was helpful, but was targeted towards the free Gmail Account and didn't work in a Docker container. I have achieved the desired result now but I need to go back and refactor the whole thing to clean it up.
This is the new unrefactored code, which is already obsolete because I'm using a straight P12 certificate that is replaced with consuming the JSON P12 file that has the certificate in it.
Note: the password is really the password for the certificate.
public static async Task<ServiceAccountCredential> Create_GoogleCredentialsWithAccessToken(MAILSERVER ms)
{
var certBuffer = File.ReadAllBytes("projectName-xxxxxxxxxx.p12");
var certificate = new X509Certificate2(certBuffer, "notasecret", X509KeyStorageFlags.Exportable);
var serviceAccountEmail = "xxxxxxxxxx-xxxxxx.iam.gserviceaccount.com";
var userAccountEmail = "xxx@xxxxxx.com";
var credential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
User = userAccountEmail,
Scopes = new[] { GmailService.Scope.MailGoogleCom }
}.FromCertificate(certificate));
if (await credential.RequestAccessTokenAsync(CancellationToken.None))
{
var service = new GmailService(
new Google.Apis.Services.BaseClientService.Initializer()
{
HttpClientInitializer = credential
}
);
}
return credential;
}
public static async Task<SendEmailCompletedEventArgs> Send_GmailServiceAsync(ServiceAccountCredential serviceCredentials, MimeMessage message, int retryCount)
{
var currentTry = 0;
while ((currentTry < retryCount))
{
try
{
using (var client = new SmtpClient())
{
client.Connect("smtp.gmail.com", 587, SecureSocketOptions.StartTls);
client.AuthenticationMechanisms.Remove("XOAUTH2");
var oauth2 = new SaslMechanismOAuth2(serviceCredentials.User, serviceCredentials.Token.AccessToken);
client.Authenticate(oauth2);
await client.SendAsync(message);
client.Disconnect(true);
}
return new SendEmailCompletedEventArgs(null, false, null, currentTry);
}
catch (Exception ex)
{
currentTry++;
if ((currentTry >= retryCount))
{
return new SendEmailCompletedEventArgs(ex, true, null, currentTry);
}
}
}
return new SendEmailCompletedEventArgs(null, true, null, currentTry);
}
I used the MimeKit to create my message, then I called the function to authenticate and send.
using (var credentials = SendGmailServiceP12Async.Create_GoogleCredentialsWithAccessToken(ms))
{
var result = await SendGmailServiceP12Async.Send_GmailServiceAsync(await credentials, message, 5);
SendGmailServiceP12Async.ProcessSendResult(model, result);
}
This is valuable info that should be written up professionally and posted here at Code Project.
I need to rewrite this in the new format first and dial it in, before considering posting an article.
If it ain't broke don't fix it
Discover my world at jkirkerx.com
modified 3-Aug-19 19:59pm.
|
|
|
|
|
Really just a reference for me in case I need to dig it up again.
But I spent the morning refactoring the previous code. Took some time because documentation was limited to just 1 post on GitHub of a user suggesting a change to the API. So this takes a service Json file created on Google Cloud or API when you create a Service Account using the Json option, and not the PFM option, and loads the file from the root of the project directly, and creates a ServiceAccountCredential. I added a User "user: me@mydomain.com" to the Google Service Json file in which the Google API didn't seem to mind it being there. The user is my email address required for the Initializer. The MAILSERVER really just contains the name of the Google Service File. So ms.GoogleServiceFile can be replaced with the name of your file.
Not too happy with the double stream, or the use of File.ReadAllTextAsync , because I needed the user parameter that I added and the GoogleCredential.FromStream would not pass it along. I thought about making a copy of the stream, or rewinding it back to 0. The user field is in the ServiceCredentials, perhaps I just need to rename user to something else that can be read.
So far so good; it works in my Debug Docker Linux container and in my production Docker container as well.
Hope this helps the next guy looking to do the same when Google discontinues "Use less secure settings" in Gmail when they remove the option next October.
This should be backwards compatible with MVC, and I wrote this for .Net Core V2.2+ using NeGet
Google.Apis.Gmail.v1.40.2.1635 GitHub - googleapis/google-api-dotnet-client: Google APIs Client Library for .NET
MailKit V2.2
MimeKit V2.2 MimeKit
public static async Task<ServiceAccountCredential> Create_GoogleCredentialsWithAccessToken(MAILSERVER ms)
{
ServiceAccountCredential credentials = null;<br />
var serviceStream = await File.ReadAllTextAsync(ms.GoogleServiceFile);
var parameters = new NewtonsoftJsonSerializer().Deserialize<GoogleServiceApi>(serviceStream);
using (var stream = new FileStream(ms.GoogleServiceFile, FileMode.Open, FileAccess.Read))
{
ServiceAccountCredential original = (ServiceAccountCredential)
GoogleCredential.FromStream(stream).UnderlyingCredential;
var initializer = new ServiceAccountCredential.Initializer(original.Id)
{
User = parameters.user,
ProjectId = parameters.project_id,
Key = original.Key,
Scopes = new[] { GmailService.Scope.MailGoogleCom, GmailService.Scope.GmailSend }
};
credentials = new ServiceAccountCredential(initializer);
if (await credentials.RequestAccessTokenAsync(CancellationToken.None))
{
var service = new GmailService(
new BaseClientService.Initializer()
{
HttpClientInitializer = credentials
}
);
}
}
return credentials;
}
public static async Task<SendEmailCompletedEventArgs> Send_GmailServiceAsync(ServiceAccountCredential serviceCredentials, MimeMessage message, int retryCount)
{
var currentTry = 0;
while ((currentTry < retryCount))
{
try
{
using (var client = new SmtpClient())
{
client.Connect("smtp.gmail.com", 587, SecureSocketOptions.StartTls);
var oauth2 = new SaslMechanismOAuth2(serviceCredentials.User, serviceCredentials.Token.AccessToken);
client.Authenticate(oauth2);
await client.SendAsync(message);
client.Disconnect(true);
}
return new SendEmailCompletedEventArgs(null, false, null, currentTry);
}
catch (Exception ex)
{
currentTry++;
if ((currentTry >= retryCount))
{
return new SendEmailCompletedEventArgs(ex, true, null, currentTry);
}
}
}
return new SendEmailCompletedEventArgs(null, true, null, currentTry);
}
Class to code against for NewtonSoftJsonSerializer
public class GoogleServiceApi
{
public string type { get; set; }
public string user { get; set; }
public string project_id { get; set; }
public string private_key_id { get; set; }
public string private_key { get; set; }
public string client_email { get; set; }
public string client_id { get; set; }
public string auth_uri { get; set; }
public string token_uri { get; set; }
public string auth_provider_x509_cert_url { get; set; }
public string client_x509_cert_url { get; set; }
}
If it ain't broke don't fix it
Discover my world at jkirkerx.com
|
|
|
|
|
Please, can you help me.
I adapted the example above to asp.net (vb), but it records the same date.
Ex: 08/01/2019 to 08/31/2019, interval of 7 days.
The correct would be to record 08/01 ... 08/08 ... 15/08 ... 22/08 ... 29/08 ... But recorded the 5 days as 01/08.
Follows the code and thanks in advance.
Dim conexao As SqlConnection
Dim cmd As SqlCommand
Dim sql As String
Dim startDate As Date = txtDataCurso.Text
Dim endDate As Date = startDate.AddDays(txtIntervalo.Text) - "This interval is the number of days (end date - initial)"
Dim currDate As Date = startDate
Do While (currDate < endDate)
sql = "INSERT INTO tre_Eventos (Id_Curso, DataEvento) VALUES ('" & ddlCursos.SelectedValue & "', '" & endDate & "')"
currDate = currDate.AddDays(7)
Try
conexao = New SqlConnection(strConn)
conexao.Open()
cmd = New SqlCommand(sql, conexao)
cmd.ExecuteNonQuery()
conexao.Close()
SetFocus(Label1)
Label1.Visible = True
Label1.Text = "Evento Cadastrado com Sucesso"
Catch
SetFocus(Label1)
Label1.Visible = True
Label1.Text = "Ocorreu um erro!" + Err.Description
End Try
|
|
|
|
|
Bartt_dmr wrote: sql = "INSERT INTO tre_Eventos (Id_Curso, DataEvento) VALUES ('" & ddlCursos.SelectedValue & "', '" & endDate & "')"
Don't do it like that!
Your code is almost certainly vulnerable to SQL Injection[^]. NEVER use string concatenation to build a SQL query. ALWAYS use a parameterized query.
You'll need to execute your query for each date you want to insert. Currently, you're only executing it for the end date.
Wrap your command and connection objects in Using blocks so that they're cleaned up properly.
And you should probably wrap the whole thing in a transaction - either all of the dates are inserted, or none of them are. You don't want to be in a position where the loop fails part-way through and you don't know what state your database is in.
Const sql As String = "INSERT INTO tre_Eventos (ID_Curso, DataEvento) VALUE (@ID_Curso, @DataEvento)"
Using conexao As New SqlConnection(strConn)
conexao.Open()
Using transaction As SqlTransaction = conexao.BeginTransaction()
Using cmd As New SqlCommand(sql, conexao)
cmd.Transaction = transaction
cmd.Parameters.AddWithValue("@ID_Curso", ddlCursos.SelectedValue)
Dim pDate As New SqlParameter("@DataEvento", SqlDbType.DateTime)
cmd.Parameters.Add(pDate)
Dim currDate As Date = startDate
Do While currDate < endDate
pDate.Value = currDate
cmd.ExecuteNonQuery()
currDate = currDate.AddDays(7)
Loop
End Using
transaction.Commit()
End Using
End Using
Everything you wanted to know about SQL injection (but were afraid to ask) | Troy Hunt[^]
How can I explain SQL injection without technical jargon? | Information Security Stack Exchange[^]
Query Parameterization Cheat Sheet | OWASP[^]
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
my App is a published website. This error happened when I save the data. Please help.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.
Source Error:
An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.
Stack Trace:
[NullReferenceException: Object reference not set to an instance of an object.]
Money_Changer.Cusinfo.BtnSave_Click(Object sender, EventArgs e) in C:\Users\Zeeyana\source\repos\Money Changer\Money Changer\Cusinfo.aspx.cs:138
System.Web.UI.WebControls.Button.OnClick(EventArgs e) +9782310
System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument) +204
System.Web.UI.WebControls.Button.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(String eventArgument) +12
System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument) +15
System.Web.UI.Page.RaisePostBackEvent(NameValueCollection postData) +35
System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +1639
darwinahmed
|
|
|
|
|
You already asked this question[^] in the Quick Answers sections. Posting it on several forums will not help you to get a quicker answer; please do not cross-post your questions.
enum HumanBool { Yes, No, Maybe, Perhaps, Probably, ProbablyNot, MostLikely, MostUnlikely, HellYes, HellNo, Wtf }
|
|
|
|
|
This is a very, very, easy problem to solve and only you can do it since we can't run your code. You are trying to access something that is null. Debug your code and you'll find it right away. Simple.
Social Media - A platform that makes it easier for the crazies to find each other.
Everyone is born right handed. Only the strongest overcome it.
Fight for left-handed rights and hand equality.
|
|
|
|
|
I have a VB.Net application using Windows authentication and I would like to implement impersonation. I think that if I can set the value of HttpContext.Current.User to a new username, I can impersonate who ever I wish. Please let me know if this is the correct approach and if so how do I change the value of HttpContext.Current.User.
Thanks in advance.
|
|
|
|
|
|
Apart from what Richard mentioned, I believe you will only mess up with the framework.
Can you share the underlying goals to impersonate the users, so that maybe we can provide you with a better alternative for the approach?
The sh*t I complain about
It's like there ain't a cloud in the sky and it's raining out - Eminem
~! Firewall !~
|
|
|
|
|
No, you cannot impersonate that way. That would be a HUGE security hole. Just google asp.net impersonation. It is very easy to do and a couple of ways of doing it. One via web.config and another via code.
Social Media - A platform that makes it easier for the crazies to find each other.
Everyone is born right handed. Only the strongest overcome it.
Fight for left-handed rights and hand equality.
|
|
|
|
|
Greetings experts,
When I run my app with the following code:
Private Sub SearchCustomers()
Dim constr As String = ConfigurationManager.ConnectionStrings("constr").ConnectionString
Dim startDate As DateTime
Dim EndDate As DateTime
Using con As New SqlConnection(constr)
Using cmd As New SqlCommand()
Dim sql As String = "spGetLogs"
cmd.CommandType = CommandType.StoredProcedure
cmd.Parameters.AddWithValue("@uuid", suuid.Text)
cmd.Parameters.AddWithValue("@callerlist", caller_list_id.Text)
cmd.Parameters.AddWithValue("@phone", phonenumber.Text)
If DateTime.TryParseExact(date_start.Text, "yyyy-MM-dd HH:mm:ss", Nothing, System.Globalization.DateTimeStyles.None, startDate) Then
cmd.Parameters.AddWithValue("@start", startDate)
Else
cmd.Parameters.AddWithValue("@start", DBNull.Value)
End If
If DateTime.TryParseExact(date_end.Text, "yyyy-MM-dd HH:mm:ss", Nothing, System.Globalization.DateTimeStyles.None, EndDate) Then
cmd.Parameters.AddWithValue("@Endd", EndDate)
Else
cmd.Parameters.AddWithValue("@Endd", DBNull.Value)
End If
cmd.Parameters.AddWithValue("@calltype", call_type.SelectedValue)
'Response.Write(sql)
'Response.End()
cmd.CommandText = sql
cmd.CommandTimeout = 600
cmd.Connection = con
Using sda As New SqlDataAdapter(cmd)
Dim dt As New DataTable()
sda.Fill(dt)
gvCustomers.DataSource = dt
gvCustomers.DataBind()
End Using
End Using
End Using
End Sub
It just keeps timing out.
However, if I run the query used in the spGetLogs stored procedure to get a count of records, it is only 191,000.
Is there something wrong with the code below?
I will be happy to post the query inside the spGetLogs stored procedure.
I have been thrust into a demo of this app on Monday.
In fact, we went through this with the users a couple of hours ago and now I run into this huge issue.
Any assistance is greatly appreciated.
|
|
|
|
|
All your code does is call SQL. So, no, the problem is not in the code. You'll have to debug your sql to find out why it is slow.
Social Media - A platform that makes it easier for the crazies to find each other.
Everyone is born right handed. Only the strongest overcome it.
Fight for left-handed rights and hand equality.
|
|
|
|
|
This may be too late, but …
Try this:
1) Move the Dim dt as New DataTable() before the Using sda statement
2) Move the gvCustomers.DataSouce and Databind statement after the end Using
3) Leave the sda.Fill(dt) inside the Using … end Using block
My theory is that the databinding is taking too long and subsequently the SQL fails because everything is wrapped inside the Using … end Using block.
Just a theory. Give it a shot and post back your results.
|
|
|
|
|
Hi all,
Im looking for information but I dont know if there is a special phase or term to describe it.
Basically, I am building a little Ticketing/Helpdesk system. I do not want to hard code the Questions/Forms. I want an admin to be able to create a Questions/FOrms under a specific Subject.
What should i be searching for to find information on this?
Thanks
Chris
Chris Wright
|
|
|
|
|
This is more a design issue.
You need to store the questions somewhere, usually a database.
You then need to create a form so the admin can create the the questions, storing them in your data store.
You then need to collect the responses for each question and store them in your data store.
After that you have to create the workflow that will support your business requirement.
All of the above can be achieved with winforms and a database. I would probably search for survey or help desk or bug tracking examples.
Never underestimate the power of human stupidity -
RAH
I'm old. I know stuff - JSOP
|
|
|
|
|
Thank you for the reply. It gives me an idea of how the stucture needs to be. I need to make sure I get the database right. It's going to be very complex.
Im going to struggle figuring how to separate the data into tables and then link it all back together to display it within the front end.
Chris Wright
|
|
|
|
|
Take a look at http://www.databaseanswers.org/data_models/[^] and see if there is one that meets your needs. It is a good site to study the structures of databases.
And yes put lot of effort into designing your data structure, think through your requirements before you start creating your database.
Never underestimate the power of human stupidity -
RAH
I'm old. I know stuff - JSOP
|
|
|
|
|
In the first DropdownList should display the Countries and in the second should display Cities based on the Country like .. USA then display just cities in USA . well i am getting undefined in dropdown city the Question is why i am getting always undefined in City dropDown list ?
here is countrol :
<pre> public ActionResult Index()
{
Details();
return View();
}
public void Details (
{
var model = db.GetUniversities().ToList();
List<SelectListItem> li = new List<SelectListItem>();
li.Add(new SelectListItem { Text = "Please select Country",Value="0"});
foreach (var item in model)
{
li.Add(new SelectListItem { Text = item.Country, Value = item.Id.ToString()});
ViewBag.state = li;
}
}
[HttpPost]
public JsonResult GetCity (int id)
{
var ddlCity = db.GetUniversities().Where(x => x.Id == id).ToList();
List<SelectListItem> licities = new List<SelectListItem>();
if (ddlCity != null)
{
foreach (var x in ddlCity)
{
licities.Add(new SelectListItem { Text = x.City, Value = x.Id.ToString() });
}
}
return Json(new SelectList(licities, "Text", "Value"));
}
here view
<div class="form-group">
@Html.LabelFor(model => model.Country, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownListFor(model => model.Country, ViewBag.state as List<SelectListItem>, new { style = "width: 200px;" })
@Html.ValidationMessageFor(model => model.Country, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.City, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownListFor(model => model.City, new SelectList(string.Empty, "Value", "Text"), "--Select City--", new { style = "width:200px" })
@Html.ValidationMessageFor(model => model.City, "", new { @class = "text-danger" })
here jQuery
$(document).ready(function () {
$("#Country").change(function () {
$("#City").empty();
$.ajax({
type: 'POST',
url: '@Url.Action("GetCity")',
dataType: 'json',
data: { id: $("#Country").val() },
success: function (city) {
$.each(city, function (i, city) {
$("#City").append('<option value="' + city.Value + '">'+ city.Text + '</option>');
});
},
error: function (ex) {
alert('Failed.' + ex);
}
});
return false;
})
});
|
|
|
|
|
You may have to set an ID attribute to your DropDownListFor control so you can reference them in your jQuery code. For example:
@Html.DropDownListFor(model => model.Country, ViewBag.state as List<SelectListItem>, { @id="ddlCountry", @class="YourCSSClassName" })
then in your jQuery, you can access it like this:
$("#ddlCountry").change(function () {
});
|
|
|
|
|
i am still getting the same error .. where should i exactly write the id .. could you more explain please
i thank you very much in advanced
|
|
|
|
|
You need to give ID's to all your controls that you reference in your jQuery and then use the IDs just like what I demonstrated. If you followed that and you are still getting error, then post your updated code here and show us which line you are getting the error.
|
|
|
|
|
Hi Vincent,
so i am still getting the same problem thus let me show you what i have changed .
i did what you have told me . could you check once again and in SQL there are some Countries and Cities like USA -> Bosten , Germany -> Frankfurt etc....
the View :
<div class="form-group">
@Html.LabelFor(model => model.Country, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownListFor(model => model.Country, ViewBag.state as List<SelectListItem>
, new {@id ="ddlCountry", @class = "form-control" })
<div class="form-group">
@Html.LabelFor(model => model.City, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownListFor(model => model.City, new SelectList(string.Empty, "Value", "Text"), "--Select City--",
new {@id ="ddlCity", @class = "form-control" })
The jQuery :
$(document).ready(function () {
$("#ddlCountry").change(function () {
$("#ddlCity").empty();
$.ajax({
type: 'POST',
url: '@Url.Action("GetCity")',
dataType: 'json',
data: { id: $("#ddlCountry").val() },
success: function (city) {
$.each(city, function (i, city) {
$("#ddlCity").append('<option value="' + city.Value + '">'+ city.Text + '</option>');
});
},
error: function (ex) {
alert('Failed.' + ex);
}
});
return false;
})
});
and with Controls haven't changed :
public void Details ()
{
var model = db.GetUniversities().ToList();
List<SelectListItem> li = new List<SelectListItem>();
li.Add(new SelectListItem { Text = "Please select Country",Value="0"});
foreach (var item in model)
{
li.Add(new SelectListItem { Text = item.Country, Value = item.Id.ToString()});
ViewBag.state = li;
}
}
[HttpPost]
public JsonResult GetCity (int id)
{
var ddlCity = db.GetUniversities().Where(x => x.Id == id).ToList();
List<SelectListItem> licities = new List<SelectListItem>();
licities.Add(new SelectListItem { Text = "--Select City--", Value = "0" });
if (ddlCity != null)
{
foreach (var x in ddlCity)
{
licities.Add(new SelectListItem { Text = x.City, Value = x.Id.ToString() });
}
}
return Json(new SelectList(licities, "Text", "Value"));
|
|
|
|
|
What error are you getting now? Still undefined? Please be specific also for the errors that you are currently getting.
|
|
|
|
|
it's fine now . i have got it .
i thank you a lot
|
|
|
|
|