Передавать в макет данные, общие для всех страниц


124

У меня есть веб-сайт, на котором есть макет страницы. Однако на этой странице макета есть данные, которые все модели страниц должны предоставлять такой заголовок страницы, имя страницы и место, где мы на самом деле находимся для HTML-помощника, который я сделал, который выполняет некоторые действия. Также каждая страница имеет свои собственные свойства модели представления.

Как я могу это сделать? Кажется, что набирать макет - плохая идея, но как передать эту информацию?


10
Для тех, кто читает здесь ответы, см. Stackoverflow.com/a/21130867/706346, где вы увидите гораздо более простое и аккуратное решение, чем все, что размещено здесь.
Avrohom Yisroel

5
@AvrohomYisroel хорошее предложение. Однако я предпочитаю подход @Colin Bacon, потому что он строго типизирован, а не в ViewBag. Возможно дело в предпочтениях. Проголосовали за ваш комментарий
JP Hellemons

для mvc 5 см. этот ответ: stackoverflow.com/a/46783375/5519026
Лаз Зия

Ответы:


143

Если вам необходимо передать одни и те же свойства на каждую страницу, тогда будет разумным создать базовую модель представления, которая будет использоваться всеми вашими моделями представления. Тогда ваша страница макета может принять эту базовую модель.

Если за этими данными требуется логика, то ее следует поместить в базовый контроллер, который используется всеми вашими контроллерами.

Вы можете многое сделать, но важный подход - не повторять один и тот же код в нескольких местах.

Изменить: обновить из комментариев ниже

Вот простой пример, демонстрирующий концепцию.

Создайте базовую модель представления, от которой унаследуются все модели представления.

public abstract class ViewModelBase
{
    public string Name { get; set; }
}

public class HomeViewModel : ViewModelBase
{
}

Ваша страница макета может принять это как образец.

@model ViewModelBase
<!DOCTYPE html>
<html>
    <head>
        <meta name="viewport" content="width=device-width" />
        <title>Test</title>
    </head>
    <body>
        <header>
            Hello @Model.Name
        </header>
        <div>
            @this.RenderBody()
        </div>
    </body>
</html>

Наконец, установите данные в методе действия.

public class HomeController
{
    public ActionResult Index()
    {
        return this.View(new HomeViewModel { Name = "Bacon" });
    }
}

12
Но данные используются в макете. Как передать данные в макет?
Рушино 05

2
Отлично! Я увидел свою ошибку. Забыл передать модель в просмотр .. какая хромая ошибка. Спасибо!
Рушино 05

7
проблема с этим подходом состоит в том, что иногда не каждое представление имеет ViewModel, поэтому в этом случае это не сработает: O /
Cacho Santa

17
Но разве для этого не требуется, чтобы каждый контроллер и каждое действие содержали код {Name = "Bacon"}? И если бы я хотел добавить еще одно свойство в ViewModelBase, мне пришлось бы переходить к каждому контроллеру и каждому действию и добавлять код для заполнения этого свойства? Вы упомянули: «Если требуется логика [...], ее следует поместить в базовый контроллер [...]». Как это сработает, чтобы устранить этот повторяющийся код в каждом контроллере и в каждом действии?
Ли

5
@Lee Если это общие данные на всех страницах, вы бы поместили их в базовый контроллер. Затем ваши контроллеры наследуют от этого базового контроллера. напр public class HomeController : BaseController. Таким образом, общий код нужно написать только один раз, и его можно будет применить ко всем контроллерам.
Колин Бэкон

73

Я использовал HTML-помощник RenderAction для бритвы в макете.

@{
   Html.RenderAction("Action", "Controller");
 }

Мне это было нужно для простой струны. Итак, мое действие возвращает строку и легко записывает ее. Но если вам нужны сложные данные, вы можете вернуть PartialViewResult и модель.

 public PartialViewResult Action()
    {
        var model = someList;
        return PartialView("~/Views/Shared/_maPartialView.cshtml", model);
    }

Вам просто нужно поместить вашу модель в начало частичного представления _maPartialView.cshtml, которое вы создали.

@model List<WhatEverYourObjeIs>

Затем вы можете использовать данные в модели в этом частичном представлении с помощью html.


18
Это, безусловно, лучший ответ!
gingerbreadboy

@gingerbreadboy согласился, что это способствует хорошей инкапсуляции и разделению проблем.
A-Dubb

35

Другой вариант - создать отдельный класс LayoutModel со всеми свойствами, которые вам понадобятся в макете, а затем поместить экземпляр этого класса в ViewBag. Я использую метод Controller.OnActionExecuting для его заполнения. Затем в начале макета вы можете извлечь этот объект из ViewBag и продолжить доступ к этому строго типизированному объекту.


1
На самом деле это звучит как наименее болезненное решение, есть ли недостатки? +1
формат

2
Однозначно лучшее решение, минусов я не вижу.
Wiktor Zychla

7
Я не понимаю, что это вам дает. Если у вас есть класс со всеми свойствами, необходимыми для макета, зачем добавлять его в ViewBag только для того, чтобы вернуть его обратно? Используйте модель в виде компоновки, вы все равно можете заполнить модель OnActionExecuting. Использование ViewBag также означает, что вы теряете безопасность типов в вашем контроллере, что никогда не бывает хорошо.
Колин Бэкон,

3
Это дает мне возможность добавлять модель для компоновки без необходимости реструктурировать все модели для наследования от единой «супер» модели во всех методах всех контроллеров в уже существующем проекте. Если вы начинаете с нуля, вы можете вместо этого извлечь все свои модели из общего корня.
DenNukem

5
@ColinBacon Еще одно преимущество этой опции заключается в том, что ваши действия не обязательно должны иметь модели представления. Кроме того, я бы сказал, что разработчикам необходимо знать, что они всегда должны наследовать свои модели представления от базовой, является недостатком.
Джош Ной

28

Предположительно, основным вариантом использования этого является получение базовой модели для представления для всех (или большинства) действий контроллера.

Учитывая это, я использовал комбинацию нескольких из этих ответов, в первую очередь опираясь на ответ Колина Бэкона.

Верно, что это все еще логика контроллера, потому что мы заполняем модель представления, чтобы вернуться к представлению. Таким образом, правильное место для этого - в контроллере.

Мы хотим, чтобы это происходило на всех контроллерах, потому что мы используем это для страницы макета. Я использую его для частичных представлений, которые отображаются на странице макета.

Мы также по-прежнему хотим получить дополнительное преимущество от строго типизированной ViewModel.

Таким образом, я создал BaseViewModel и BaseController. Все контроллеры ViewModels наследуют от BaseViewModel и BaseController соответственно.

Код:

BaseController

public class BaseController : Controller
{
    protected override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        base.OnActionExecuted(filterContext);

        var model = filterContext.Controller.ViewData.Model as BaseViewModel;

        model.AwesomeModelProperty = "Awesome Property Value";
        model.FooterModel = this.getFooterModel();
    }

    protected FooterModel getFooterModel()
    {
        FooterModel model = new FooterModel();
        model.FooterModelProperty = "OMG Becky!!! Another Awesome Property!";
    }
}

Обратите внимание на использование OnActionExecuted, взятого из этого сообщения SO

HomeController

public class HomeController : BaseController
{
    public ActionResult Index(string id)
    {
        HomeIndexModel model = new HomeIndexModel();

        // populate HomeIndexModel ...

        return View(model);
    }
}

BaseViewModel

public class BaseViewModel
{
    public string AwesomeModelProperty { get; set; }
    public FooterModel FooterModel { get; set; }
}

HomeViewModel

public class HomeIndexModel : BaseViewModel
{

    public string FirstName { get; set; }

    // other awesome properties
}

FooterModel

public class FooterModel
{
    public string FooterModelProperty { get; set; }
}

Layout.cshtml

@model WebSite.Models.BaseViewModel
<!DOCTYPE html>
<html>
<head>
    < ... meta tags and styles and whatnot ... >
</head>
<body>
    <header>
        @{ Html.RenderPartial("_Nav", Model.FooterModel.FooterModelProperty);}
    </header>

    <main>
        <div class="container">
            @RenderBody()
        </div>

        @{ Html.RenderPartial("_AnotherPartial", Model); }
        @{ Html.RenderPartial("_Contact"); }
    </main>

    <footer>
        @{ Html.RenderPartial("_Footer", Model.FooterModel); }
    </footer>

    < ... render scripts ... >

    @RenderSection("scripts", required: false)
</body>
</html>

_Nav.cshtml

@model string
<nav>
    <ul>
        <li>
            <a href="@Model" target="_blank">Mind Blown!</a>
        </li>
    </ul>
</nav>

Надеюсь, это поможет.


2
Я использовал этот подход, но предпочитаю наследование от интерфейса, а не от базового класса. Итак, я сделал: var model = filterContext.Controller.ViewData.Model как IBaseViewModel if (model! = Null) {model.AwesomeModelProperty = "Awesome Property Value"; }
Том Геркен

2
отличный ответ, я предпочел этот всем другим.
Jynn

1
Отличный ответ, но у меня есть вопрос. «Что, если у меня есть представления, в которых нет ViewModels ...?»
Isma Haro

Пробовал это, но в действии индекса OnActionExecuted заполняет FooterModel, а затем создается новый HomeIndexModel с нулевым FooterModel :(
SteveCav

1
@drizzie: в вашем базовом контроллере модель является локальной переменной в методе Filter: var model = filterContext.Controller.ViewData.Model как BaseViewModel. Я не понимаю, как MVC понимает, что эта локальная переменная совпадает с моделью, которую HomeController отправляет для просмотра.
Hooman Bahreini

9

Вам не нужно возиться с действиями или изменять модель, просто используйте базовый контроллер и приведите существующий контроллер из контекста представления макета.

Создайте базовый контроллер с желаемыми общими данными (заголовок / страница / местоположение и т. Д.) И инициализацией действия ...

public abstract class _BaseController:Controller {
    public Int32 MyCommonValue { get; private set; }

    protected override void OnActionExecuting(ActionExecutingContext filterContext) {

        MyCommonValue = 12345;

        base.OnActionExecuting(filterContext);
    }
}

Убедитесь, что каждый контроллер использует базовый контроллер ...

public class UserController:_BaseController {...

Отобразите существующий базовый контроллер из контекста представления на вашей _Layout.cshmlстранице ...

@{
    var myController = (_BaseController)ViewContext.Controller;
}

Теперь вы можете ссылаться на значения в базовом контроллере со страницы макета.

@myController.MyCommonValue

ОБНОВИТЬ

Вы также можете создать расширение страницы, которое позволит вам использовать this.

//Allows typed "this.Controller()." in cshtml files
public static class MyPageExtensions {
    public static _BaseController Controller(this WebViewPage page) => Controller<_BaseController>(page);
    public static T Controller<T>(this WebViewPage page) where T : _BaseController => (T)page.ViewContext.Controller;
}

Тогда вам нужно только не забыть использовать this.Controller()контроллер, когда вы хотите.

@{
    var myController = this.Controller(); //_BaseController
}

или конкретный контроллер, наследуемый от _BaseController...

@{
    var myController = this.Controller<MyControllerType>();
}

Что эквивалентно этому в ядре .NET? Поскольку ViewContext.Controller отсутствует и есть некоторые изменения в цепочке наследования
Джаянт Тьягараджан

4

если вы хотите передать всю модель, сделайте следующее в макете:

@model ViewAsModelBase
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta charset="utf-8"/>
    <link href="/img/phytech_icon.ico" rel="shortcut icon" type="image/x-icon" />
    <title>@ViewBag.Title</title>
    @RenderSection("styles", required: false)    
    <script type="text/javascript" src="http://code.jquery.com/jquery-1.8.3.min.js"></script>
    @RenderSection("scripts", required: false)
    @RenderSection("head", required: false)
</head>
<body>
    @Html.Action("_Header","Controller", new {model = Model})
    <section id="content">
        @RenderBody()
    </section>      
    @RenderSection("footer", required: false)
</body>
</html>

и добавьте это в контроллер:

public ActionResult _Header(ViewAsModelBase model)

4

Я не думаю, что какой-либо из этих ответов достаточно гибок для большого приложения уровня предприятия. Я не фанат чрезмерного использования ViewBag, но в этом случае для гибкости я бы сделал исключение. Вот что бы я сделал ...

У вас должен быть базовый контроллер на всех ваших контроллерах. Добавьте свои данные макета OnActionExecuting в свой базовый контроллер (или OnActionExecuted, если вы хотите отложить это) ...

public class BaseController : Controller
{
    protected override void OnActionExecuting(ActionExecutingContext     
        filterContext)
    {
        ViewBag.LayoutViewModel = MyLayoutViewModel;
    }
}

public class HomeController : BaseController
{
    public ActionResult Index()
    {
        return View(homeModel);
    }
}

Затем в вашем _Layout.cshtml вытащите ViewModel из ViewBag ...

@{
  LayoutViewModel model = (LayoutViewModel)ViewBag.LayoutViewModel;
}

<h1>@model.Title</h1>

Или...

<h1>@ViewBag.LayoutViewModel.Title</h1>

Это не влияет на кодирование контроллеров вашей страницы или моделей представлений.


Мне нравится ваша идея, но как насчет того, если у вас есть MyLayoutViewModelдинамически созданный, как я могу передать некоторые параметры OnActionExecutingметоду?
Rajmond Burgaj 01

1
Эээ, вам еще нужен base.OnActionExecuting(filterContext)ваш OnActionExecutingметод !!!
ErikE

4

Создание базового представления, представляющего модель представления макета, - ужасный подход. Представьте, что вам нужна модель, которая представляет навигацию, определенную в макете. Вы бы сделали CustomersViewModel : LayoutNavigationViewModel? Зачем? Почему вы должны передавать данные модели навигации через каждую модель представления, которая есть в вашем решении?

Модель представления макета должна быть выделена сама по себе и не должна заставлять остальные модели представления зависеть от нее.

Вместо этого вы можете сделать это в своем _Layout.cshtmlфайле:

@{ var model = DependencyResolver.Current.GetService<MyNamespace.LayoutViewModel>(); }

Самое главное, нам это не нужно, new LayoutViewModel()и мы получим все зависимости, которые LayoutViewModelбыли разрешены за нас.

например

public class LayoutViewModel
{
    private readonly DataContext dataContext;
    private readonly ApplicationUserManager userManager;

    public LayoutViewModel(DataContext dataContext, ApplicationUserManager userManager)
    {
    }
}

Где заливать эту модель? В BaseController тоже?
ndberg

Я полагаю, что это также было бы хорошей идеей для Scopedобъекта модели макета в ASP..Net Core.
Джеймс Уилкинс

Я бы не хотел, чтобы представление получало зависимость. Это определенно не MVC. Service Locator - это анти-шаблон .
Jiveman

Вопреки распространенному мнению, Service Locator не является анти-шаблоном, и на самом деле это не имеет ничего общего с MVC, вы просто добавляете модные слова @Jiveman? blog.gauffin.org/2012/09/service-locator-is-not-an-anti-pattern
hyankov

Основная мысль Джгауффина в этой статье, по-видимому, заключается в том, что термин «антипаттерн» не следует применять к Service Locator, поскольку могут быть по крайней мере некоторые допустимые варианты использования SL. Справедливый вопрос. Однако, как видно из некоторых его собственных комментариев к обсуждению, он предполагает, что, хотя SL может быть допустимым подходом при создании библиотек и фреймворков, его не обязательно рекомендовать при создании приложений (что я бы рассмотрел вопрос OP и это обсуждение здесь как вращается вокруг).
Jiveman

3

Другие ответы охватили почти все о том, как мы можем передать модель на нашу страницу макета. Но я нашел способ, с помощью которого вы можете динамически передавать переменные на страницу макета без использования какой-либо модели или частичного представления в макете. Допустим, у вас есть эта модель -

public class SubLocationsViewModel
{
    public string city { get; set; }
    public string state { get; set; }
}

И вы хотите получить динамично развивающийся город и штат. Например,

в вашем index.cshtml вы можете поместить эти две переменные в ViewBag

@model  MyProject.Models.ViewModel.SubLocationsViewModel
@{
    ViewBag.City = Model.city;
    ViewBag.State = Model.state;
}

А затем в вашем layout.cshtml вы можете получить доступ к этим переменным viewbag

<div class="text-wrap">
    <div class="heading">@ViewBag.City @ViewBag.State</div>
</div>

это отлично работает, @stun_Gravy есть ли отказ от использования ViewBag для передачи данных, таких как роль или уровень доступа пользователя?
3not3

3

Есть другой способ справиться с этим. Возможно, это не самый чистый способ с архитектурной точки зрения, но он позволяет избежать боли, связанной с другими ответами. Просто вставьте службу в макет Razor, а затем вызовите метод, который получает необходимые данные:

@inject IService myService

Затем позже в виде макета:

@if (await myService.GetBoolValue()) {
   // Good to go...
}

Опять же, не чисто с точки зрения архитектуры (очевидно, что сервис не следует вводить непосредственно в представление), но он выполняет свою работу.


Не самый чистый способ? Я не согласен. Я думаю, что это настолько чисто, насколько это возможно: объект передается из места, где он был создан, прямо в место, где вы хотите, чтобы он был, без «загрязнения» других контроллеров элементами, которые им не нужно видеть. На @injectмой взгляд, использование - лучшее решение.
dasblinkenlight

1
Если подумать об этом больше, возможно, ты прав. Тот факт, что этот метод позволяет избежать такой сильной боли, свидетельствует о том, что, возможно, это самый чистый способ. Я работал над очень большим приложением ASP.NET Core и использую этот шаблон для таких вещей, как логика навигационной цепочки, данные заголовка, которые есть на большинстве страниц, и тому подобное. Вместо этого я сделал так, чтобы избежать такой боли.
Эндрю

2

Вы также можете использовать RenderSection , он помогает вам вводить Modelданные в _Layoutпредставление.

Вы можете вводить View Modelданные, Json, Script, CSS, и HTMLт.д.

В этом примере я вводю Jsonиз Indexпредставления в Layoutпредставление.

Index.chtml

@section commonLayoutData{

    <script>

        var products = @Html.Raw(Json.Encode(Model.ToList()));

    </script>

    }

_Layout.cshtml

@RenderSection("commonLayoutData", false)

Это избавляет от необходимости создавать отдельную Базу View Model .

Надежда кому-то помогает.


1
Идеальное решение , когда вы только нужно сделать что - то конкретное для нескольких представлений.
Кунал

1

то, что я сделал, очень просто и работает

Объявите свойство Static в любом контроллере, или вы можете создать класс данных со статическими значениями, если хотите, например:

public static username = "Admin";
public static UserType = "Administrator";

Эти значения могут обновляться контроллерами в зависимости от операций. позже вы можете использовать их в своем _Layout

В _layout.cshtml

@project_name.Controllers.HomeController.username
@project_name.Controllers.HomeController.UserType

1
Всегда полезно добавить пояснение к своему ответу, чтобы он был более ясным и понятным. Прочтите stackoverflow.com/help/how-to-answer .
32cupo,

0

Почему никто не предложил методы расширения для ViewData?

Опция 1

Мне кажется, что это наименее навязчивое и простое решение проблемы. Никаких жестко запрограммированных строк. Никаких наложенных ограничений. Никакого волшебного кодирования. Никакого сложного кода.

public static class ViewDataExtensions
{
    private const string TitleData = "Title";
    public static void SetTitle<T>(this ViewDataDictionary<T> viewData, string value) => viewData[TitleData] = value;
    public static string GetTitle<T>(this ViewDataDictionary<T> viewData) => (string)viewData[TitleData] ?? "";
}

Установить данные на странице

ViewData.SetTitle("abc");

Вариант # 2

Другой вариант, упрощающий объявление поля.

public static class ViewDataExtensions
{
    public static ViewDataField<string, V> Title<V>(this ViewDataDictionary<V> viewData) => new ViewDataField<string, V>(viewData, "Title", "");
}

public class ViewDataField<T,V>
{
    private readonly ViewDataDictionary<V> _viewData;
    private readonly string _field;
    private readonly T _defaultValue;

    public ViewDataField(ViewDataDictionary<V> viewData, string field, T defaultValue)
    {
        _viewData = viewData;
        _field = field;
        _defaultValue = defaultValue;
    }

    public T Value {
        get => (T)(_viewData[_field] ?? _defaultValue);
        set => _viewData[_field] = value;
    }
}

Установите данные на странице. Объявление проще, чем первый вариант, но синтаксис использования немного длиннее.

ViewData.Title().Value = "abc";

Вариант # 3

Затем можно объединить это с возвратом одного объекта, содержащего все поля, связанные с макетом, со значениями по умолчанию.

public static class ViewDataExtensions
{
    private const string LayoutField = "Layout";
    public static LayoutData Layout<T>(this ViewDataDictionary<T> viewData) => 
        (LayoutData)(viewData[LayoutField] ?? (viewData[LayoutField] = new LayoutData()));
}

public class LayoutData
{
    public string Title { get; set; } = "";
}

Установить данные на странице

var layout = ViewData.Layout();
layout.Title = "abc";

У этого третьего варианта есть несколько преимуществ, и я считаю, что в большинстве случаев он является лучшим вариантом:

  • Простейшее объявление полей и значений по умолчанию.

  • Самый простой синтаксис использования при установке нескольких полей.

  • Позволяет устанавливать различные типы данных в ViewData (например, макет, заголовок, навигация).

  • Допускает дополнительный код и логику в классе LayoutData.

PS Не забудьте добавить пространство имен ViewDataExtensions в _ViewImports.cshtml


0

Вы можете создать файл бритвы в папке App_Code, а затем получить к нему доступ со страниц просмотра.

Проект> Repository / IdentityRepository.cs

namespace Infrastructure.Repository
{
    public class IdentityRepository : IIdentityRepository
    {
        private readonly ISystemSettings _systemSettings;
        private readonly ISessionDataManager _sessionDataManager;

        public IdentityRepository(
            ISystemSettings systemSettings
            )
        {
            _systemSettings = systemSettings;
        }

        public string GetCurrentUserName()
        {
            return HttpContext.Current.User.Identity.Name;
        }
    }
}

Проект> App_Code / IdentityRepositoryViewFunctions.cshtml:

@using System.Web.Mvc
@using Infrastructure.Repository
@functions
{
    public static IIdentityRepository IdentityRepositoryInstance
    {
        get { return DependencyResolver.Current.GetService<IIdentityRepository>(); }
    }

    public static string GetCurrentUserName
    {
        get
        {
            var identityRepo = IdentityRepositoryInstance;
            if (identityRepo != null)
            {
                return identityRepo.GetCurrentUserName();
            }
            return null;
        }
    }
}

Project> Views / Shared / _Layout.cshtml (или любой другой файл .cshtml)

<div>
    @IdentityRepositoryViewFunctions.GetCurrentUserName
</div>

-1

вместо этого вы всегда можете использовать другой подход, который также быстро

создайте новое частичное представление в общем каталоге и вызовите частичное представление в макете как

@Html.Partial("MyPartialView")

в вашем частичном представлении вы можете вызвать свой db и выполнить то, что вы хотите сделать

@{
    IEnumerable<HOXAT.Models.CourseCategory> categories = new HOXAT.Models.HOXATEntities().CourseCategories;
}

<div>
//do what ever here
</div>

предполагая, что вы добавили свою базу данных Entity Framework


1
Понижение, поскольку это никогда не должно быть ответственностью View для получения собственной модели.
Oxonhammer

-1

Невероятно, что здесь никто этого не сказал. Передача модели просмотра через базовый контроллер - беспорядок. Мы используем утверждения пользователей для передачи информации на страницу макета (например, для отображения пользовательских данных на панели навигации). Есть еще одно преимущество. Данные хранятся в файлах cookie, поэтому нет необходимости извлекать данные в каждом запросе через частичные данные. Просто погуглите "утверждения идентичности asp net".


@CodeSmith что? Я предлагаю решение.
макоре

Плохо, думал, что это вопрос, но теперь вижу ответ, удалено.
CodeSmith

Если это попытка ответа на вопрос, его следует уточнить. Комментарии допустимы, но они не должны доминировать над всем ответом; кроме того, советы о том, что искать в Google, не являются правильным ответом.
tripleee

-6

Вы можете использовать это так:

 @{ 
    ApplicationDbContext db = new ApplicationDbContext();
    IEnumerable<YourModel> bd_recent = db.YourModel.Where(m => m.Pin == true).OrderByDescending(m=>m.ID).Select(m => m);
}
<div class="col-md-12">
    <div class="panel panel-default">
        <div class="panel-body">
            <div class="baner1">
                <h3 class="bb-hred">Recent Posts</h3>
                @foreach(var item in bd_recent)
                {
                    <a href="/BaiDangs/BaiDangChiTiet/@item.ID">@item.Name</a>
                }
            </div>
        </div>
    </div>
</div>

7
Подключение к базе данных - действительно плохая идея.
1_bug
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.