Рейтинг:
8
Graeme_Grant
Я собрал для вас тестовое решение, чтобы продемонстрировать, как делать клиентские связи с помощью сервиса WebAPI rest. Данные не зашифрованы, так как я предполагаю, что ваш сайт будет включен HTTPS.
Пример консольного клиента демонстрирует:
* Как отправить запрос
* как читать код ответа и данные
* как разместить многослойная форма отправки данных
Пример серверного контроллера Web API демонстрирует:
* как обрабатывать полученную почту из нескольких частей формы
* как вернуть ответ вызывающему клиенту
Клиентские приложения консоль :
internal static class Program
{
private static void Main()
{
UserWaiting();
Console.WriteLine("Good TestGetMethod....");
TestGetMethod("api/values");
Console.WriteLine("Bad TestGetMethod....");
TestGetMethod("api/valuesx");
UserWaiting();
Console.WriteLine("Good TestPostMethod....");
TestPostMethod("api/values", new Dictionary<string, string> { ["valueField"] = "test post value" });
Console.WriteLine("Bad TestPostMethod....");
TestPostMethod("api/values", null);
Console.WriteLine("\n-- DONE --");
Console.ReadKey();
}
private const string host = "http://localhost:65189";
private static void TestGetMethod(string path)
{
var response = Request(new Uri($"{host}/{path}", UriKind.Absolute),
"GET");
var ms = GetResponseStream(response);
if (response.StatusCode == HttpStatusCode.OK)
{
string responseData = GetResponseString(ms);
ReportResponse(response, responseData);
}
else
{
ReportError(response, ms);
}
}
private static void TestPostMethod(string path, Dictionary<string, string> values)
{
var response = Request(new Uri($"{host}/{path}", UriKind.Absolute),
"POST",
values);
var ms = GetResponseStream(response);
if (response.StatusCode == HttpStatusCode.OK)
{
string responseData = GetResponseString(ms);
ReportResponse(response, responseData);
}
else
{
ReportError(response, ms);
}
}
private static void UserWaiting()
{
Console.WriteLine("\n-- Waiting to begin --\n");
Console.ReadKey();
}
private static void ReportError(HttpWebResponse response, MemoryStream ms)
{
if (ms != null)
{
string responseData = GetResponseString(ms);
ReportResponse(response, responseData);
}
else
{
Console.WriteLine("!! UNHANDLED ERROR ENCOUNTERED");
}
}
private static void ReportResponse(HttpWebResponse response, string responseData, bool isError = false)
{
Console.WriteLine();
Console.WriteLine($"{(isError ? "!!" : "**")} {(int)response.StatusCode} - {response.StatusDescription}\n[{responseData}]");
Console.WriteLine();
}
private static string GetResponseString(MemoryStream ms)
{
string responseData;
using (var reader = new StreamReader(ms, Encoding.ASCII))
responseData = reader.ReadToEnd();
return responseData;
}
private static MemoryStream GetResponseStream(HttpWebResponse response)
{
MemoryStream ms = null;
if (response != null)
{
using (var responseStream = response.GetResponseStream())
{
ms = new MemoryStream();
responseStream.CopyTo(ms);
ms.Position = 0;
}
}
return ms;
}
private const string seperatorLine = "--";
private const string terminateMarker = "--";
private static HttpWebResponse Request(Uri uri, string method = "GET", Dictionary<string, string> parameters = null)
{
HttpWebResponse response = null;
try
{
var clientRequest = WebRequest.Create(uri) as HttpWebRequest;
clientRequest.Method = method;
if (method == "POST" && parameters != null)
{
// post data
var postdata = BuldPostData(parameters, clientRequest);
using (var requestStream = clientRequest.GetRequestStream())
{
byte[] byte1 = (new UTF8Encoding()).GetBytes(postdata);
requestStream.Write(byte1, 0, byte1.Length);
}
}
response = clientRequest.GetResponse() as HttpWebResponse;
}
catch (WebException wex)
{
Debug.WriteLine($"! Resp::WebException: {wex.Message}");
if (wex.Response != null)
{
response = (HttpWebResponse)wex.Response; // Get Response
}
}
catch (Exception ex)
{
// error occured
Debug.WriteLine($"! Resp::Exception: {ex.Message}");
}
return response;
}
private static string BuldPostData(Dictionary<string, string> parameters, HttpWebRequest clientRequest)
{
// unique for each call...
var boundary = $"MIME_{Guid.NewGuid().ToString("N")}";
var boundaryMarker = $"{seperatorLine}{boundary}";
var boundaryTerminationMarker = $"{boundaryMarker}{terminateMarker}";
clientRequest.ContentType = $"multipart/form-data; boundary=\"{boundary}\"";
var sb = new StringBuilder();
foreach (var parameter in parameters)
{
sb.AppendLine(boundaryMarker)
.AppendLine("Content-Type: text/plain; charset=utf-8")
.Append("Content-Disposition: form-data; name=\"").Append(parameter.Key).AppendLine("\"")
.AppendLine()
.AppendLine(WebUtility.HtmlEncode(parameter.Value));
}
sb.AppendLine(boundaryTerminationMarker);
return sb.ToString();
}
}
Контроллер:public class ValuesController : ApiController
{
[HttpGet]
public HttpResponseMessage GetValues()
{
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK, "value");
response.Content = new StringContent("get hello", Encoding.ASCII);
response.Headers.CacheControl = new CacheControlHeaderValue()
{
MaxAge = TimeSpan.FromMinutes(20)
};
return response;
}
[HttpPost]
public HttpResponseMessage PostValues()
{
if (!Request.Content.IsMimeMultipartContent())
throw new HttpResponseException(HttpStatusCode.BadRequest);
var valueField = "** not set **";
var reqParams = HttpContext.Current.Request.Params;
if (reqParams.AllKeys.Contains("valueField"))
valueField = reqParams["valueField"].TrimEnd(new[] { '\r', '\n' });
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK, "value");
response.Content = new StringContent($"post hello: [{valueField}]", Encoding.ASCII);
response.Headers.CacheControl = new CacheControlHeaderValue { MaxAge = TimeSpan.FromMinutes(20) };
return response;
}
}
Пример Вывода:
-- Waiting to begin --
Good TestGetMethod....
** 200 - OK
[get hello]
Bad TestGetMethod....
** 404 - Not Found
[{"Message":"No HTTP resource was found that matches the request URI 'http://localhost:65189/api/valuesx'.","MessageDetail":"No type was found that matches the controller named 'valuesx'."}]
-- Waiting to begin --
Good TestPostMethod....
** 200 - OK
[post hello: [test post value]]
Bad TestPostMethod....
** 411 - Length Required
[<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Length Required</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Length Required</h2>
<hr><p>HTTP Error 411. The request must be chunked or have a content length.</p>
</BODY></HTML>
]
-- DONE --
Member 12719658
Большое вам спасибо за ответ. Так что с этим вызовом я предполагаю, что контроллер - это файл, расположенный в моем интернете. Если это так, то я не вижу, где он смотрит в базу данных, и если значение есть, а другое значение = 0, то аутентифицируется. Я никогда раньше не создавал API, так что извините, если я совершенно не разбираюсь в этом.
Graeme_Grant
Вы смотрели на ASP.NET веб-API[^]?
[HttpGet]
где обрабатываются только запросы данных (строки запроса URL-адреса для параметров) и данные возвращаются; [HttpPost]
/[HttpPut]
это место, где данные (параметры, передаваемые в теле, а не в строке запроса) загружаются на сервер для добавления/обновления данных на сервере; [HttpDelete]
(не в примере кода) предназначен для удаления данных на сервере.
Итак, чтобы ответить на ваш вопрос, вы можете использовать либо [HttpGet]
или [HttpPost]
для вашего сценария. Приведенный выше пример управления возвращает строки, но может быть легко заменен кодом базы данных.
Member 12719658
Отлично! Спасибо за ссылку на API. Это будет огромная помощь. С вашим примером и помощью по этой ссылке я думаю, что смогу это закрыть.
Member 12719658
Глядя на мой сайт-godaddy взимает плату за HTTPS, которую я сейчас не могу себе позволить. Если мой сайт не будет включен HTTPS прямо сейчас, мне просто нужно будет правильно зашифровать код API?
Member 12719658
Спасибо вам за эту ссылку. Теперь я уверен, что это должен быть другой вопрос форума, но прямо сейчас я использую godaddy с выделенным сервером на сервере Linux. Я вижу, что letsencrypt у вас должен быть включен apache, и это поставляется с Linux godaddy, однако вместо этого я могу перейти на Windows server, чтобы добавить некоторые онлайн-формы. Знаете ли вы о SSL на хост-сервере Windows?
Graeme_Grant
Погуглите вокруг ... там будет бесплатный, который будет соответствовать вашим потребностям. :)
Member 12719658
Я смотрел много tuts и думаю, что у меня есть работающий API. По крайней мере, я это делаю на своем компьютере. Теперь мне нужно поместить его в интернет, однако я не уверен, где и какой будет веб-адрес? Какие файлы выходят в интернет и как мне поместить другие файлы в программу WPF, чтобы, когда пользователь нажимает кнопку, он использовал API и проверял?
Graeme_Grant
Пожалуйста, начните новый вопрос для этого.
Рейтинг:
1
HKHerron
Я сделал что-то очень похожее на это в предыдущем проекте.
Это можно легко сделать с помощью MySql, который обычно также устанавливается на большинстве веб-серверов.
Сначала вам нужно будет настроить базу данных MySql на вашем веб-сервере. Назовите это "myapp_keycodes".
Создайте 1 таблицу (myapp_KeyCodes), и я предлагаю, чтобы она имела по крайней мере 4 поля: KeyCode, Usage, User и Date.
Ключ - это поле должно содержать код и значение Primary & уникальная.
Использование-это может быть Bool (True / False) или целое число (0/1)
Пользователь-адрес электронной почты пользователя, устанавливающего ваше приложение.
Дата-дата установки приложения.
Затем в вашем приложении WPF добавьте текстовый файл с именем "dbConfig.txt".
Внутри текстового файла вы должны указать сервер, имя базы данных, Пользователя и пароль, используемые для подключения к базе данных. Поместите каждый пункт в новую строку. см., например, ниже:
99.214.49.191 or http://www.mywebsite.com
myapp_KeyCodes
myapp_user1
Password12345
Это может быть жестко закодировано, но затруднит работу, если вы измените веб-серверы.
Затем добавьте в свой проект следующий файл. Назовите его DBConnect.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.IO;
using MySql.Data.MySqlClient;
namespace myApp
{
partial class DBConnect
{
private MySqlConnection connection;
private string server;
private string database;
private string uid;
private string password;
#region Load Config file information
//Constructor
public DBConnect()
{
// Change the path to your location in the next line
var filestream = new FileStream(@"c:\Configfolder\dbConfig.txt",
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite);
var file = new StreamReader(filestream, Encoding.UTF8, true, 128);
string lineOfText;
int x = 1;
while((lineOfText = file.ReadLine()) != null)
{
if (x == 1) { server = lineOfText; }
if (x == 2) { database = lineOfText; }
if (x == 3) { uid = lineOfText; }
if (x == 4) { password = lineOfText; }
x++;
}
Initialize();
}
#endregion
#region Initialize
//Initialize values
private void Initialize()
{
// If hard coding database information, put it here and do NOT run DBConnect above.
//server = "http://www.mywebsite.com";
//database = "myApp_keycodes";
//uid = "myapp_user1";
//password = "Password12345";
string connectionString = "SERVER=" + server + ";" + "DATABASE=" + database + ";" + "UID=" + uid + ";" + "PASSWORD=" + password + ";";
connection = new MySqlConnection(connectionString);
}
#endregion
#region Encypt/Decrypt
//encrypt
public string Base64Encode(string plainText)
{
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
return System.Convert.ToBase64String(plainTextBytes);
}
//decrypt
public string Base64Decode(string base64EncodedData)
{
var base64EncodedBytes = System.Convert.FromBase64String(base64EncodedData);
return System.Text.Encoding.UTF8.GetString(base64EncodedBytes);
}
#endregion
#region Open Database Connection
//open connection to database
public bool OpenConnection()
{
try
{
connection.Open();
return true;
}
catch (MySqlException ex)
{
//When handling errors, you can use your application's response based on the error number.
//The two most common error numbers when connecting are as follows:
//0: Cannot connect to server.
//1045: Invalid user name and/or password.
switch (ex.Number)
{
case 0:
MessageBox.Show("Cannot connect to server. Contact administrator");
break;
case 1045:
MessageBox.Show("Invalid username/password, please try again");
break;
case 1042:
MessageBox.Show("Unable to connect to any of the specified MySQL hosts., please try again");
break;
}
return false;
}
}
#endregion
#region Close Database Connection
//Close connection
public bool CloseConnection()
{
try
{
connection.Close();
return true;
}
catch (MySqlException ex)
{
MessageBox.Show(ex.Message);
return false;
}
}
#endregion
#region Get Key Code information
//Gets KeyCode Information from Database on web server.
public boolean getKeyCode(string kcode)
{
string query = "SELECT * FROM myapp_KeyCodes where KeyCode = '" + kcode + "'";
//Open connection
if (this.OpenConnection() == true)
{
try
{
//Create Command
MySqlCommand cmd = new MySqlCommand(query, connection);
//Create a data reader and Execute the command
MySqlDataReader dataReader = cmd.ExecuteReader();
//Read the data return by the database
while (dataReader.Read())
{
string kc = dataReader["KeyCode"].ToSting();
int usage = dataReader["Usage"];
//or bool usage = dataReader["Usage"];
string uName = dataReader["User"].ToString();
string date = dataReader["Date"].ToString();
}
//close Data Reader
dataReader.Close();
if(kcode == kc)
{
if(usage == 0) // If Bool, then use: If(usage == false)
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
catch
{
// Error retrieving database information, or no information was found.
return false;
}
}
else
{
return false;
}
}
#region Update Keycode Information.
//Update KeyCode Info
public void UpdateKeyCode(string kCode, string nUser, string IDate, Int nUsage)
{
string query = "UPDATE myapp_KeyCodes SET User='" + nUser + "', Date='" + IDate + "', Usage='" + nUsage + "' WHERE KeyCode =" + kCode;
//Open connection
if (this.OpenConnection() == true)
{
//create mysql command
MySqlCommand cmd = new MySqlCommand();
//Assign the query using CommandText
cmd.CommandText = query;
//Assign the connection using Connection
cmd.Connection = connection;
//Execute query
cmd.ExecuteNonQuery();
//close connection
this.CloseConnection();
}
}
#endregion
}
}
Хорошо, теперь приходит время использовать это в вашем приложении.
Например, это может быть внутри вашей кнопки отправки или кнопки установки, которая находится в окне, где она просит пользователя ввести код ключа.
private void Button_Click(object sender, RoutedEventArgs e)
{
DBConnect myDBConnect = new DBConnect();
bool checkCode = getKeyCode(txt_KeyCode.text); //Get key code from text box
if(checkCode == True)
{
// KeyCode already Used
// Display Warning...
// Allow retry or Exit application.
return;
}
else
{
// Ask user for Email address to register the Key Code
// Allow Installation to complete.....
// then Update Web Server Database with new usage
UpdateKeyCode(txt_KeyCode.text, txt.UserEmail.Text, DateTime.Now.ToString(), 1)
}
Я постарался собрать все это для тебя довольно быстро.
Могут быть какие-то ошибки типа o или другие ошибки, но они должны дать вам представление о том, как это работает. Я действительно использовал класс DBConnect в нескольких своих приложениях, и он, кажется, работает очень хорошо.
Я буду готов помочь вам и дальше, если вы не сможете заставить это работать.
Удачи! :)
Member 12719658
Спасибо за ваш ответ. Я проверю это и проверю, есть ли у меня какие-либо проблемы. Всегда, когда дело доходит до такого типа предмета - я хочу проверить все, чтобы увидеть, в каком направлении мне нужно идти.
Member 12719658
Вопрос - с помощью этого кода он оставит строку подключения в коде. Будет ли это уязвимо, если строка подключения сервера к моей онлайн-базе данных будет жестко закодирована?
HKHerron
Существует несколько различных способов обработки строки подключения.
К сожалению, требуется подключение к любой базе данных MySql.
Текстовый файл был просто простым способом демонстрации.
Я использую версию текстового файла, потому что я использую ее в нескольких приложениях, и они подключаются к разным серверам. Таким образом, я могу использовать тот же код, и единственное, что меняется, - это мой файл DBConfig. Это не считается жестко закодированным, так как он загружает информацию из файла DBConfig каждый раз, когда приложение запускается. И да, это не очень безопасно. Однако, чтобы сделать текстовый файл более безопасным,вы можете зашифровать его. Затем расшифруйте в коде, чтобы прочитать информацию.
Вы также можете сделать его частью установочного приложения, жестко закодировать его в метод Initialize (), а затем сохранить в любом месте и в любой форме. Как только ваша заявка будет удовлетворена, найти нужную информацию будет намного сложнее.
Еще один способ-создать веб-приложение, которое выполняет функции MySql, так что строка подключения фактически находится на веб-сервере, а не в вашем коде.
например, написать веб-страницу:
http://www.mysite.com/CheckKeyCode?a1b2c3d4e5f6g7h8i9j10k11
Пусть он выполнит ту же работу, что и в моем коде, а затем вернет пустую страницу либо с работой true, либо false.
Затем мы меняем мой код, чтобы запустить httprequest с введенным кодом ключа, а затем читаем ответ. Затем выполните действия, основанные на истинном или ложном ответе.
И последнее, о чем я забыл упомянуть, - создайте другого пользователя для базы данных, кроме вашей основной учетной записи или учетной записи администратора. Тогда только дайте пользователю права на запрос и обновление.
Member 12719658
Что касается этого кода - я пытаюсь проверить, и я получаю много ошибок, что имена не существуют в текущем контексте, а также имена не могут быть найдены. Если у вас есть время - не могли бы вы Просмотреть код и/или прислать мне демо-решение?
HKHerron
Хорошо, есть некоторые вещи, которые вам нужно изменить, чтобы он работал правильно.
Например, пространство имен должно соответствовать вашему приложению.
Member 12719658
Спасибо за ответ. Да, пространство имен действительно соответствует моему приложению. Эту часть я не копировал и не вставлял.
Ошибки теперь только в отношении:
1. Не удается неявно преобразовать тип объекта '' в 'int' (использование инт = объект DataReader["использование"];)
2. (в классе DBConnect)имя "kc" не существует в текущем контексте
3. (в классе DBConnect)имя "usage" не существует в текущем контексте
4. (в окне WPF)имя 'getKeyCode' не существует в текущем контексте
5. (в окне WPF) Имя "UpdateKeyCode" не существует в текущем контексте
Graeme_Grant
Такой подход небезопасен-подвержен взлому. Прочтите мои комментарии к ОП ниже.
Graeme_Grant
Такой подход небезопасен. Если пользователь имеет доступ к dbConfig.txt файл, то ваша база данных может быть скомпрометирована/взломана! Кроме того, хакеры любят этот тип доступа к базам данных с низким уровнем безопасности!
В целях безопасности между клиентом и БД должен быть серверный уровень.
Member 12719658
Полностью понимать.
Graeme_Grant
Есть сценарии, где этот тип решения хорош, но не подходит для того, что вы пытаетесь сделать.
Сценарии, на которые я ссылаюсь, обычно проходят через VPN (виртуальную частную сеть) или выделенные фиксированные IP-адреса (в общественном достоянии / Интернете). Оба используют более безопасный метод доступа с паролем.
HKHerron
Я согласен, я действительно утверждал, что это небезопасный подход.
Однако я упомянул об использовании серверного решения, которое вместо этого предварительно сформирует эту функцию на сервере, а затем ответит приложению, действителен ли код ключа или нет.
Однако у меня есть к вам пара вопросов (ничего общего с безопасностью)
Как вы планируете вести базу данных?
Вы собираетесь добавлять новый код ключа каждый раз, когда выдаете очередную копию?
Какой тип веб-сервера вы используете? Он публичный?
Graeme_Grant
Это зависит от того, где он размещен и какая система баз данных используется. Как поддерживать? Бы с одним из двух указанных решений - VPN или заблокированных IP-адресов. Тогда вы можете использовать любой инструмент. При использовании EF (Entity Framework) он включает в себя уже встроенную миграцию БД.