收益的主要思想:开发汽车共享服务的应用程序服务,并在地图上显示可用的汽车。在内容中 - 可以与Vehicles本质进行比较的所有内容:汽车,摩托车,任何车辆等。该应用程序应具有多功能:必须拥有大量页面,具有接收,添加,更改和删除实体的能力。
pager -A添加到页面上。httpContextAccessor与用户合作。UserStatusProvider状态的存储设施的过渡。使用SweetAlert2服务添加JS消息。ReadMe一起工作。Stripe支付服务的测试模式下连接到项目。将时间和日期选择的功能添加到页面上的放置。JS代码的位置(由文件砸碎)。Brief Description 。他开始与汽车的评级一起工作。.NET 7 。在目录页面的搜索引擎上工作。RepositoryProvider替换大量不同的存储库,并降低了所有功能。JWToken Authentication添加到Web -Application Controllers。JWToken并在客户端设置错误的页面显示。JSON文件到MongoDB Local的存储。MongoDB Atlas群集,以进一步与云服务合作。UI上工作。将Google Maps API标记从红色更改为带有名称和图像的汽车。实习的正式开始
Web应用程序过渡到完整的Clean Architecture 。Domain Layer并设置第一个型号。模型是在Rich Domain Models样式中创建的。 Anemic Domain Models 。Application和Infrastructure Layers ,并与服务建立首次交互并设置DI 。PublicAPI一起创建的层。endpoints及其手动测试。Errors Handling及其处理的开始。Vehicle型号的一部分中添加Errors Handling 。endpoints的测试。Azure Key Vault功能以获取Azure的秘密数据。实施咬合标志是存储有关汽车信息的选项之一。CustomerController及其手动测试添加新的endpoints 。VehicleController及其手动测试中添加新的endpoints 。IHttpClientFactory ,以创建客户向PublicAPI发送申请的客户。对于非范围的发送消息的Polly配置,有可能在严重情况下等待和重新待发。PublicApi与Web部件一起绑定,检查端点以及序列化序列化和避难所。JWToken ,并成功进入了帐户。UI页面上工作。errors handling 。html页面和一些endpoints 。Bootstrap 5+新版本,并删除注册和授权页面的逻辑。GoogleMaps Api和错误校正。Appsettings.json文件。设置和与Duende Identity Server第一次交互。Identity Server的规则,并从不必要的文件中清理项目。Duende Identity Server绑定到Web Application (客户端部件)。Duende Identity Server一起过渡到使用自定义-MADE JWT-Authorization和创建Dashboard页面(用户个人帐户)。MongoDB Entities合作的Duende Identity Server 。Duende Identity Server 。将AzureAD设置为授权方式之一。将新实体添加到MSSQL Server 。Identity Server设置有关的文件中清理项目。JwtBearer进行授权调整。设置AzureAD 。连接Azure Blob Storage是存储汽车图像的机制之一。Pipeline 。添加ActionNotes Repository作为应用程序的服务之一。Dashboard页面上显示有关客户的一些数据有关的新机会。更新了添加了新列的数据库的结构。 Pipeline配置为接收数据以提交有关客户端帐户的信息。Dashboard页面上工作。Dashboard页面上设置与车辆有关的操作。添加ajax功能以下载没有更新的页面。根据标准添加了搜索功能。pipeline供应以显示目录中汽车的数据。GoogleMaps Api 。Stripe支付服务添加集成。VehicleInformation页面时,使用软件方法。Azure VM合作。在AzureVM上设置Seq并托管异常服务。将海军应用程序访问到您的MSSQL数据库中。Main branch Merge时进行GitHub Actions和自动Publish工作。Http转移到Https并添加新功能的工作。Rental和Payment模式以及Azure VM情绪。Stripe Payment Service进行交互。Stripe相关的错误。在创建Stripe Checkout Sessions上的工作。Requests和Responses 。Runtime处理并获得表格。DateTime错误纠正,数据库中的请求以及应用程序功能。Routing-ом和设置页面。.tagets文件,以解决不同项目中同一软件包版本的问题。Endpoints上工作以编辑汽车数据。pipeline最终配置。server response分析仪上工作,并设置这些responses的通用处理程序。DateTime.Now (局部时间)转换为对象DateTime.UtcNow (UTC Time)。Routing设置为REST样式。pipeline上进行编辑信息。该项目是一个完整的Web应用程序,与PublicAPI在同一级别上运行,以Clean Architecture风格编写,该应用程序允许任何拥有有效的租赁卡和驾驶执照的人,可以将另一个用户的汽车送上一段时间,但也没有注册,但不是租用别人的汽车,但可以为他们的汽车提供其他用户提供汽车。因此,应用程序的开发人员可以获得优势,收到用户进行的一定百分比,并直接将用户自己租用或租赁汽车。
Stripe集成)SweetAlert2 )Google Maps API )Stripe )ErrorOr NuGet Package )JWT Bearer NuGet Package )Azure Key Vault )Azure Blob Storage )Azure AD )Seq Service )Polly )Lets Encrypt Service )PVS Studio )MSSQL和MongoDB )GitHub Actions和Action Runners )Azure VM )还有其他...
整个项目都是从头开始编写的,使用bootstrap 5+来装饰视觉组件,并使用Razor Pages的功能,嗯, C# .NET Core来编写backend组件。作为互动模型,我选择了MVC 。在出口,我们正在处理Client-Server MVC Wep App 。

一旦未经授权的用户启动了该应用程序,他就会立即看到主屏幕,其中包含有关该服务的信息。此页面包含所有必要的信息,以使自己熟悉服务的所有功能。最重要的是,您可以看到navbar元素,该元素始终在任何页面上,并且不会从旨在执行附录中用户导航的视野中消失。 Navbar提供以下机会:

另外,在页面上有一张来自Google的地图,该地图显示了所有可用的位置,而不是从目录中租用的汽车(标记出现在地图上,根据该路,可以跟踪车辆的当前位置。当将其托付给标记时,将出现带有其名称的车辆的图像)。作为限制,我选择了明斯克市,即,地图的位置是覆盖整个城市地区的方式。如果说汽车将在布雷斯特街上的某个地方,那么我们将不会在地图上看到它(或者有必要更改卡的规模)。作为放置汽车的可用地点,我决定将自己限制在白俄罗斯,也就是说,由授权用户放置的汽车应该在白俄罗斯共和国。但是,没有什么可以阻止用户在另一个城市甚至一个国家创建汽车的订单。
关于卡本身,它使用特殊钥匙连接到应用程序,该键无需根据著名的纬度和经度找到位置的任何限制。

无论页面如何,在最底部,都会有有关应用程序开发人员的信息,并链接到相应的社会。 SAM解决方案网络和资源。

在与目录的页面上,将有机会查看有关汽车的所有负担得起的简短信息,并在目录中提供的汽车中搜索标准。在页面上有机会:

关于汽车的信息,提供了目录:

该页面还有机会引起实施对车辆的积分搜索所必需的形式。在这里,向用户提供了许多不同的过滤器,他可以在目录中找到他感兴趣的汽车。
因此,该目录允许您考虑出现的车辆的各种,并为您的口味和颜色选择任何副本。
用户按下Log In后,他立即将其重定向到授权页面。

在未经授权的用户的授权页面上,您需要输入电子邮件(或登录)和密码以成功授权。当填写字段的错误时,填写时将显示错误消息:

如果所有字段都正确填写,但授权尚未通过,则用户将提供有关授权失败的消息:

有了成功的授权,已经授权的用户将被重定向到仪表板页面(个人帐户),该页面将有有关成功授权的消息,该消息将在出现后3秒自动消失。

如果用户没有自己的帐户,则用户需要创建自己的帐户,将通过右Navbar元素上的Sign Up按钮按下执行的页面过渡。


在新用户注册的页面上,有必要输入一系列数据,以便该程序将客户端输入数据库,并且注册成功。整个页面都充满了验证,如果某些内容未通过,则不会通知用户发生错误的字段(与授权的蓬松相同)。成功注册后,用户将被重定向到注册页面,其中将有有关成功注册的消息。
用户成功进入他新创建的帐户后,他将获得机会,不仅可以查看目录中有关汽车的更多详细信息,还可以添加自己的汽车,以及对其他用户使用汽车的行动和行动的跟踪统计信息。


在此页面上,用户将必须提供有关他的汽车的相关信息,并设定租用汽车的关税。该页面还具有验证(类似于授权和注册页面)。但是,一旦用户成功共享他的汽车,它将立即出现在他的帐户中,但是,为了出版,管理员必须批准该应用程序,然后用户可以通过其个人帐户发布汽车,并以各种可能的方式与此汽车进行编辑。 (由于用户添加了他的汽车,因此在目录中向同一用户展示它是不完全合乎逻辑且正确的。但是,他们仍然显示在目录中,但是通过使用有关这辆车的信息进入页面上,所有者将无法租用它,但是他将能够熟悉他的其他用户的描述和代表)。
在授权用户的仪表板页面上,建议乞求干燥其帐户的统计数据。在此页面上,用户可以查看此类信息:
从仪表板页面中,用户有机会使用有关汽车的编辑信息或编辑帐户信息的页面进入页面。

主页允许用户更改与他的帐户关联的某些字段(例如名称,姓氏,昵称,邮件等)
按下2个有迷你风的左侧菜单在左侧有几个按钮,这些按钮也可以更改帐户的一些信息。

单击头页上的相应按钮时,请邀请用户选择代表他的个人资料的一个化身之一。用户选择一个化身之一,然后他将单击Apply并Save changes按钮,将更新Avatar,并且用户将能够在其帐户中观察另一个图像。

按下下一个按钮时,用户将有机会从他的帐户中输入新密码,此后将完全更新其帐户的密码,并且在重新输入帐户时,旧密码将无效。

用户将新车添加到该帐户后,管理员将成功确认他后,用户将能够发布他的汽车(他将成为其他用户的刺激性租金),或将其隐藏。如果汽车被隐藏,则单击相应的菜单时,用户将能够单击相应的Modify按钮,然后使用有关汽车的编辑信息转到页面。
在此页面上,他将能够更改有关汽车的总体信息(但是,他将无法更改汽车的形象及其类别,以至于如果他有这样的机会,在管理员确认了汽车后,他可以加载任何图像,此后其他用户在其目录中看到了错误和通用信息)。
TODO:更改以下所有内容。

一旦授权用户按目录中的车辆上的Information按钮,他就会使用所选汽车打开相应的页面,并提供有关该副本的更多详细信息。从此页面上,很快就有可能在用户的个人帐户中记录下来的订单。
授权后,我添加了一个将过渡到用户个人帐户的按钮,该按钮允许用户跟踪其订单和添加的汽车(到目前为止,只有Publish和Hide按钮可以与汽车交互)。


在此页面上,用户不仅可以查看和更改他的信息,还可以控制他的汽车和订单。在用户数据之后,您可以看到以下计数器:
另外,在此页面上,您可以查看此类信息:
使用汽车桌时,您可以观察到3种颜色:
根据汽车的状态和颜色,新/旧功能向用户打开/阻止
如上所述,对于授权用户的系统,可以输入他的个人帐户并跟踪与他的订单有关的所有信息,以及该用户为我们服务的其他用户提供的汽车。但是,如果未输入某些信息,如果用户的数据已更改……或者发布汽车的用户可以在其他人的背景下以某种方式выделться ???为了解决这些问题,我开发了有关当前用户及其汽车的编辑信息的页面:
从您的个人帐户中,转到页面,并使用有关您自己的编辑信息,用户可以单击相应的Edit Profile按钮

按下时,用户打开了一个可以更改页面上显示的信息的页面

特别是:其配置文件的头像(从提出的7个图像中选择(1个默认图像,分配给每个新用户,还有6个供您选择))

您也可以更改密码。我以一种不需要的方式建立了一个新密码(实际上,必须在安装新密码之前请求真实密码)

从一个简单的内容中,用户可以更改此页面上的任何字段(例如,配置文件的描述,他的电话号码,电子邮件等)。更改所有字段用户后,您必须单击“相应的Save Changes按钮(在安装新密码时按下Save按钮时,将自动安装新密码)。如果在填写字段时犯了某种错误,则用户可以单击Cancel Chnages按钮,该按钮将返回页面为原始表单(对旧数据的图案),或按Get Back ”按钮,将其传输回其个人帐户。
实际上,关于汽车,用户还可以更改页面上以前的信息:

但是,更改的可用字段数量是用户的几倍。为什么呢?事实是,在发展时,这个想法发生在我身上:每辆车都会受到订单数量的监控。但是,如果用户可以完全更改汽车,请说出他的形象和名称,该怎么办?这辆车的评分将保持不变,但是详细说明将完全改变。至于我,这种方法可以在确定其他用户方面发挥关键作用,例如:例如,一年前放置汽车的用户显然会损失将其旧车描述变为更新的用户的用户。因此,我得出的结论是,信息需要隐藏在编辑中,并且只有最必要的字段可用:具有关税,描述和位置的字段。
授权用户的Dubo可以查看目录中其他用户的汽车的详细信息。从每辆车的页面中,用户可以通过使用此车的订单进入页面:

在此页面上,他被邀请选择一个可以使用汽车的时间段。

时间间隔的选择是使用我安装的daterangepicker元素进行的:开始时间 - 向下一个小时。结束时间(默认)是开始时间 + 1小时。因此,使用汽车的最小时间为1小时。我设定的狡猾时间是自开始以来的7天。付款金额的计算是根据公式进行的:乘以关税/天 +小时数 *关税/小时的天数。用户做所有必要的准备工作并确认订单后,他将被重定向到付款页面:

在付款页面上,为了再次重新订单,提供了用户所需的所有信息。付款后,用户将能够在其个人帐户中找到汽车:拥有汽车的用户将看到汽车被租用了,租用汽车的用户将看到有关付费时间以及有关租赁租约剩余时间和时间的所有信息。看起来如下:

当用户决定提前完成订单(该订单并不以负责过期订单的系统而结束,而是下达订单的用户)时,他有机会设置他在第n个时间内使用的汽车评级:

同样,用户可以选择:将汽车的评分放置,然后按相应的Submit and Finish按钮,并使用评分完成订单,或通过单击Finish and not submit按钮来跳过评分,然后在不发送等级的情况下完成订单。
评级本身可以在页面上看到有关汽车的信息。根据用户设定的评估,一般统计信息将显示在页面上。上面可以看到具有0星的汽车,下面显示了带有一个用户的带额定评级的汽车的页面:下面显示:

在本节中,我将尝试描述,但是如何从内部安排项目,“黑匣子”内部发生了什么。对于那些至少对C编程语言和编程方面有一点了解的人,这将有助于更好地理解该项目。
当然,为了使用任何数据,对于初学者,您需要决定,但是如何存储它们? Of course, super large projects are used for storage such a database as Oracle, PostgreSQL, MySQL, MSSQL and others. However, the problem of this approach is that access to applications tied to databases can only be obtained if the connection to the same database can be obtained on the user's device (for such a function you have to pay large amounts of money to gigants). Since I do not have large amounts of money, in order to start the project to any user and not experience problems with its testing and use, I put forward the idea of using serialization and decering in the contexts of relative paths. Thus, the problems with obtaining access from users who downloaded the application from the repository should not arise.
Каким же образом происходит получение данных из JSON-файлов и как вообще устроено получение данных? (Для всех локальных хранилищ)
Для того, чтобы не привязываться к какому-то определённому типу хранилищ, мной был применён подход Dependency Injection, а именно внедрение Singleton зависимостей между хранилищами и локальным репозиторием программы. То есть: в моей программе есть интерфейс:
public interface IVehiclesRepositoryЭтот интерфейс будет являться связующим звеном для получения данных. В данном интерфейсе есть набор методов, которые должны быть реализованы для того или иного сервиса, чтоб им можно было пользоваться независимо от реализации методов. Главное, чтоб эти методы были реализованы в том классе, который решит реализовать этот интерфейс. Для реализации локального интерфейса, так как я исользую локальное хранилище данных в файлах, а не в БД, я решил использовать Singleton подход, что будет означать, что объект сервиса будет создаваться только при первом обрпщении к нему, после чего все последующие запросы будут проходить через тот самый сервис. Ниже, мы явно указываем, что если кто-то захочет получить объект этого сервиса через интерфейс, мы дадим ему конкретную реализацию (В данном случае реализацию локального репозитория):
builder . Services . AddSingleton < IVehiclesRepository , VehiclesLocalRepository > ( ) ; // Где требуется IVehiclesRepository - дай реализацию VehiclesLocalRepositoryСами же локальные репозитории реализованы таким образом, чтоб уменьшить количество загрузок из файлов в само приложение. Как только происходит первое обращение к сервису - производится работа метода SetUpLocalRepository. Этот метод запишет в путой объект List<> модели, считанные из JSON - файла, после чего все манипуляции будут проходить непосредственно через сам этот List<>, а если данные будут меняться - будет вызываться асинхронный метод SaveChanges(), который призван асинхронно записать изменения в файл (Повторное считывание файла не происходит, так как мы работаем с объектом List<>, который мы также изменили перед тем, как запрашивать обновление JSON-файла). Таким образом, применённый мной подход не только позволит пользователю получать доступ в кратчайшие сроки, но и позволит избежать излишних нагрузок системы для загрузки данных из файлов каждый раз при обращении к сервису.
При проектировнии, мной была выявлена следующая проблема - А должен ли пользователь, добавивший автомобиль в каталог, иметь возможность взаимодействовать с тем же самым автомобилем из каталога? Конечно же нет. Такой подход позволит пользователю, добавившему автомобиль в каталог, арендовать свой же автомобиль (В чём смысл???). Для избежания этой проблемы, мной был выбран следующий подход:
Представим, что на данный момент, нет ни пользователей, ни добавленных автомобилей. Вот мы запускаем наше приложение. На главной странице мы видим карту с 0 маркерами, а в каталоге также 0 автомобилей. Мы создаём новый аккаунт, входим в него, делимся своим автомобилем.但! Автомобиль не появляется в каталоге.问题是什么? Дело в том, что перед тем, как отобразить автомобиль в каталоге, добавивший его пользователь должен явно опубликовать автомобиль из своего личного кабинета путём нажатия кнопки Publish напротив выбранного автомобиля. Как только он произведёт публикацию, для этого пользователя в каталоге ничего не изменится, он также будет видеть 0 автомобилей.然而! Если мы выйдем из аккаунта или создадим новый - В каталоге будет виднеться тот самый автомобиль, которым поделился и который опубликовал предыдущий пользователь. Таким образом, пользователь, добавивший автомобиль и разместивший его в каталог, не может увидеть свои же собственные автомобили (Все свои добавленные автомобили пользователь может увидеть в своём личном кабинете).但! После того, как пользователь поделится своим автомобилем и разместит его в каталог из личного кабинета, хотя он не видит этот автомобиль в каталоге, он может перейти на главную страницу и обратить внимание, что его автомобиль появился на карте в виде маркера (Однако в каталоге его по-прежнему нет). Дело в том, что система показывает ЛЮБОМУ пользователю ВСЕ автомобили на карте в виде маркера, которые были ОПУБЛИКОВАНЫ в личном кабинете не зависимо от того, какой пользователь сейчас находится в системе. Однако в каталоге, система показывает те же самые автомобили, что изображены на карте на главной странице в виде маркеров, НО ещё проверяется условие, что ID владельца автомобиля не равно ID текущего пользователя, вошедшего в аккаунт. Для неавторизованного пользователя ID не проверяется. Ему показываются те же самые автомобили в каталоге, что и на карте на главной странице.
Таким образом, подводя итог:
Publish (И пропадёт, если он решит убрать его из каталога путём нажатия кнопки Hide ) При добавлении автомобиля, на странице использованы такие технологии, как локальное хранилище данных (Local Storage) для хранения координат местоположения автомобиля, а также специальные скрипты и элемент C# IFormFile для получения файла изображения со сраницы.
Local Storage
Предположим, что мы - очень невнимательные пользователи, которые всё время допускают ошибки на страницах. Для того, чтобы избежать повторного ввода координат каждый раз при обновлении страницы, пной было принято решение хранить координаты GoogleMaps в локальном хранилище и чистить эти данные в случае покидания пользователем страницы добавления своего автомобиля. Так как данные, применяемые при работе с GoogleMaps являются специфическими (представление типа данных float отличаются знаком разделителя) чтоб избежать большого количества манипуляции с данными, используется локальное хранилище, которое призвано сократить число операция приведения данных из одного представления в другое.
IFormFile и скрипты
При работе с изображеним, мы не можем получать доступ к файловой системе пользователя со страницы Razor, так как это просто недопустимо в рамках работы системы безопасности, поэтому приходится использовать определённые подходы, например, как работа с IFormFile. Объекты этого типа хранят всю необходимую информацию о выбранном изображении, которое выберет пользователь, при этом не представляет никакой угрозы для файловой системы компьютера в целом. Однако использование такого подхода имеет недотаток - пользователю необходимо каждый раз выбирать изображение снова и снова, если пользователем повторно допускаются ошибки на странице добавления автомобиля.
Ниже я постарался описать интересные технические моменты, которые я предпринимал в течение проектирования и разработки проекта.
При работе с получением автомобилей из репозитория у меня было 2 идеи, как решить данную задачу: Либо при каждом обращении к контроллеру формировать новый объект с информацией о машинах и передавать его во View , либо же принимать этот объект из View , если он уже был передан однажды, при этом не делая никаких дополнительных запросов в репозиторий, и модифицировать согласно предпочтениям пользователя по количеству отображения автомобилей на странице и тд. Сначала я принял решение пойти через получение модели из View , однако столкнулся с такой проблемой, как Model Binding , которая просто так не даёт получить List переданных в качестве модели автомобилей: Либо нужно использовать Ajax , при этом заранее сериализовать модель в JSON и после чего десериализовать полученную JSON -строку в Controller-е , либо никак) Поэтому я выбрал первый путь (через создание объекта), так как в этом случае придётся манипулировать в основном со ссылками на данные, нежели чем с самими данными. (ps Также, работа через первый подход потребовала бы накладных расходов на проверку, а не добавилась ли в репозиторий новая машина, но уже другим пользователем, и если добавилась, также необходимо было бы добавить её в модельку, пришедшую из View )
Изначально, мной была заложена идея, что пользователь может как оформить заказ на определённое время, внеся предоплату, так и просросить его, за что потребуется внести дополнительные деньги за просроченное время. Однако, при дальнейшем развитии идеи, мной были выявлены следующие проблемы, касательно как программной, так и правовой идеи такого подхода:
Поэтому, мной был выбран следующий план действий: Подразумевается, что заказ начинается, как только пользователь оформляет заказ на пользование авто и оплачивает его, а заканчивается этот же заказ ровно тогда, когда время пользования станет равно оплаченному времени. После чего, автомобиль сразу становится доступным для других пользователей в каталоге, а нынешний заказ пропадает с личного кабинета пользователя, оплатившего время на его использование. Если другой пользователь оформит заказ на тот же автомобиль и при прибытии на место обнаружит, что автомобиль отсутствует на том месте, на котором он расположен на карте, он имеет право подать в суд на человека, который просрочил своё время (собственно как и заказчик). Таким образом, каждый пользователь, который оформляет заказ, берёт на себя ответственность закончить его во время.