Ответы пользователя по тегу ASP.NET
  • Почему с view на контроллер не идет модель?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Скорее всего имена HTML-элементов получаются неправильные (одинаковые). Попробуйте через обычный for, тогда на выходе должны будут получаться имена с указанием индекса (типа: [0].Html, [1].Html и т.п.):
    @for (int i = 0, count = Model.Count; i < count; ++i)
    {
      @Html.TextAreaFor(m => m[i].Html, new { @class = "Width500", @style = "height:130px;" })
    }
    Ответ написан
    1 комментарий
  • Калькулятор на mvc с историей операций?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Если для веб и именно на ASP.NET, то можно:

    1. Реализовать структуру или класс для хранения элемента данных (истории) и использовать статический класс, свойство или поле (например, на уровне класса контроллера) для всей истории:

    public class HistoryItem 
    {
    
      public double X { get; set; }
    
      public double Y { get; set; }
    
      public string Operation { get; set; }
    
      // предполагается, что журнал подразумевает хранение истории вычислений,
      // и хотя можно повторно провести вычисления, в журнале такого по идее быть не должно,
      public double Result { get; set; }
    
    }
    
    // ...
    
    public static List<HistoryItem> History = new List<HistoryItem>();
    
    // ...
    
    History.Add(new HistoryItem { X = 123, Y = 456, Operation = "+", Result =  579 });
    History.Add(new HistoryItem { X = 5, Y = 5, Operation = "*", Result = 25 });

    или проще, без HistoryItem, записывать в виде строк:

    public static List<string> History = new List<string>();
    
    // ...
    
    History.Add("2 * 2 = 4");
    History.Add("3 + 4 = 7");

    Можно сделать поправку на многопоточность и использовать что-нибудь потокобезопасное:

    using System.Collections.Concurrent;
    
    // ...
    
    public static ConcurrentBag<string> History = new ConcurrentBag<string>();
    
    // ...
    
    History.Add("123 + 345 = 468");
    History.Add("7 * 7 = 49");

    Но при такой реализации, история будет доступна только в рамках жизненного цикла приложения.

    2. Записывать в текстовой файл:

    // добавить
    System.IO.File.AppendText("history.log", "5 * 5 = 25");
    // прочитать историю
    // System.IO.File.ReadAllText("history.log");

    При такой реализации, файл истории будет доступен в рамках жизненного цикла устройства хранения данных, пока последняя резервная копия не будет утрачена :-)

    3. Записывать в базу данных.

    Примерно такой же вариант, как и с записью в файл, но более сложный.

    ---

    Если условия задачи позволяют, я бы наверное сделал на стороне клиента (JavaScript) и использовал localStorage:

    var historyData = window.localStorage.getItem('history');
    var history = historyData ? JSON.parse(historyData) : [];
    
    history.push('1 + 1 = 2');
    history.push('2 + 2 = 4');
    history.push('4 + 4 = 8');
    
    window.localStorage.setItem('history', JSON.stringify(history));
    Ответ написан
    Комментировать
  • Где применяют ASP.net сегодня?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Мне интересно в какой сфере применяют ASP

    ASP - умер, да здравствует ASP.NET! А нет, да здравствует ASP.NET Core! ;-)

    1. Любые проекты. Как правильно большие и сложные. Делать что-то простое смысла нет, для этого можно использовать что-нибудь типа PHP.

    2. Работа есть. Но новичкам нынче придется не сладко. За последние семь лет все сильно усложнилось.

    3. Можно делать все :-) Блоги, формы, порталы, магазины, платежные сервисы, корпоративные сайты. В общем, все что угодно. Мои нынешние проекты являются сложными системами, которые я уже не способен охватывать целиком и они не ограничиваются использованием ASP.NET. Но .NET доминирует, поскольку это удобно и работать просто приятно, от процесса написания кода, до отладки и тестирования.
    Ответ написан
    Комментировать
  • ASP.NET не видит статичные файлы в папке Views/Home?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Файл ведь физически находятся в папке /Views/Home/bg.jpg, но скорее всего это не будет работать. Папка /Views предназначения только для файлов представлений, частичных представлений и шаблонов. Обычная (ожидаемая) обработка файлов в этой папке должна быть по умолчанию запрещена.

    Для статичных файлов предназначена папка /Content.

    Для файлов скриптов можно использовать папку /Scripts.
    Ответ написан
    4 комментария
  • Как изменять картинки нажатием на кнопки "вперёд" "назад"?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Это WebFroms или MVC?

    Если WebFroms, то просто сделать обработчик нажатия по ссылке/кнопке, в котором установуть нужный файл в <asp:Image />.

    Например, код на странице может быть таким:
    <asp:LinkButton ID="lnkPrev" 
      Text="Назад" 
      RunAt="server"
      OnClick="lnkPrev_Click"
    />
    
    <asp:Image ID="Image1" 
      ImageUrl="~/img/1.jpg"
      RunAt="server"
    />
    
    <asp:LinkButton ID="lnkNext" 
      Text="Вперёд" 
      RunAt="server"
      OnClick="lnkNext_Click"
    />

    Код обработчиков может быть таким:

    protected void lnkPrev_Click(object sender, EventArgs e)
    {
      // получаем список файлов в папке ~/img
      var files = System.IO.Directory.GetFiles(Server.MapPath("~/img"));
      // текущий путь берем из Image1.ImageUrl
      var currentPath = Server.MapPath(Image1.ImageUrl);
    
      // ищем текущий путь среди полученных файлов
      int index = Array.IndexOf(files, currentPath);
    
      // проверяем, нашлось что-то или нет
      if (index != -1)
      {
        // проверяем, можно получить предыдущий файл или нет
        if (index - 1 > 0)
        {
          // можно, берем его
          Image1.ImageUrl = String.Format("~/img/{0}", System.IO.Path.GetFileName(files[index - 1]));
        }
        else
        {
          // нельзя, берем последний из списка
          Image1.ImageUrl = String.Format("~/img/{0}", System.IO.Path.GetFileName(files[files.Length - 1]));
        }
      }
    }
    
    protected void lnkNext_Click(object sender, EventArgs e)
    {
      var files = System.IO.Directory.GetFiles(Server.MapPath("~/img"));
      var currentPath = Server.MapPath(Image1.ImageUrl);
          
      int index = Array.IndexOf(files, currentPath);
    
      if (index != -1)
      {
        // проверяем, есть файлы впереди или нет
        if (files.Length > index + 1)
        {
          // есть, берем следующий файл
          Image1.ImageUrl = String.Format("~/img/{0}", System.IO.Path.GetFileName(files[index + 1]));
        }
        else
        {
          // нет, берем первый из списка
          Image1.ImageUrl = String.Format("~/img/{0}", System.IO.Path.GetFileName(files[0]));
        }
      }
    }


    При желании, можно сделать один обработчик для обоих ссылок/кнопок с помощью CommandArgument.
    <asp:LinkButton ID="lnkPrev" 
      Text="Назад" 
      RunAt="server"
      CommandArgument="-1"
      OnClick="LinkButton_Click"
    />
    
    <asp:Image ID="Image1" 
      ImageUrl="~/img/1.jpg"
      RunAt="server"
    />
    
    <asp:LinkButton ID="lnkNext" 
      Text="Вперёд" 
      RunAt="server"
      CommandArgument="1"
      OnClick="LinkButton_Click"
    />

    protected void LinkButton_Click(object sender, EventArgs e)
    {
      var lnk = (LinkButton)sender;
      var files = System.IO.Directory.GetFiles(Server.MapPath("~/img"));
      var currentPath = Server.MapPath(Image1.ImageUrl);
          
      int index = Array.IndexOf(files, currentPath);
    
      // если подумать, то этот блок кода можно уменьшить
      if (index != -1)
      {
        if (lnk.CommandArgument == "1")
        {
          if (files.Length > index + 1)
          {
            Image1.ImageUrl = String.Format("~/img/{0}", System.IO.Path.GetFileName(files[index + 1]));
          }
          else
          {
            Image1.ImageUrl = String.Format("~/img/{0}", System.IO.Path.GetFileName(files[0]));
          }
        }
        else
        {
          if (index - 1 > 0)
          {
            Image1.ImageUrl = String.Format("~/img/{0}", System.IO.Path.GetFileName(files[index - 1]));
          }
          else
          {
            Image1.ImageUrl = String.Format("~/img/{0}", System.IO.Path.GetFileName(files[files.Length - 1]));
          }
        }
      }
    }


    Для MVC будет совсем по-другому.

    Красивее всего это делать со стороны клиента, с помощью JavaScript.
    Ответ написан
  • Как в WebGrid asp.net mvc поменять количество выводимых страниц?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    @{ 
      var grid = new WebGrid(/*...*/);
    }
    
    @grid.GetHtml(mode: WebGridPagerModes.Numeric, numericLinksCount: 10)
    
    или отдельно список страниц:
    
    @grid.Pager(numericLinksCount: 10)

    https://msdn.microsoft.com/ru-ru/library/system.we...
    public IHtmlString GetHtml(
    	string tableStyle,
    	string headerStyle,
    	string footerStyle,
    	string rowStyle,
    	string alternatingRowStyle,
    	string selectedRowStyle,
    	string caption,
    	bool displayHeader,
    	bool fillEmptyRows,
    	string emptyRowCellValue,
    	IEnumerable<WebGridColumn> columns,
    	IEnumerable<string> exclusions,
    	WebGridPagerModes mode,
    	string firstText,
    	string previousText,
    	string nextText,
    	string lastText,
    	int numericLinksCount,
    	Object htmlAttributes
    )

    numericLinksCount
    Тип: System.Int32
    Число цифровых ссылок на ближайшие страницы WebGrid. Текст каждой цифровой ссылки на страницу содержит номер страницы. Задайте флаг Numeric параметра mode, чтобы эти элементы управления отображались на странице.

    https://msdn.microsoft.com/ru-ru/library/system.we...
    public HelperResult Pager(
    	WebGridPagerModes mode,
    	string firstText,
    	string previousText,
    	string nextText,
    	string lastText,
    	int numericLinksCount
    )

    numericLinksCount
    Тип: System.Int32
    Число отображаемых цифровых ссылок на страницы. По умолчанию используется значение 5.
    Ответ написан
  • Представления (View) в ASP.NET должен разрабатывать из чистой верстки фронтендер или бекенд-программист?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Это front-end и если в компании есть соответствующий разработчик, то это его удел.

    Серверного кода со стороны представлений не должно быть слишком много и все что будет должно быть простым. По сути просто вывод данных, элементарные условия, циклы, использование готовых методов. Любой разработчик справится, иначе не был бы разработчик разработчиком :-) Базовые знания C#, конечно, понадобятся, но для разработчика это не проблема, поскольку разработчик уже должен иметь навыки программирования и новый язык - это просто альтернативное представление уже имеющихся знаний и опыта. В данном случае, роль C# следует рассматривать как JavaScript.

    Если в проекте много клиентского кода, то лучше front-end разработчика никто не сможет обеспечить его правильную работу и использование. Если этим будет заниматься back-end разработчик, то он потратит больше времени, поскольку ему придется сильно углубляться во front-end, рискуя при этом переквалифицироваться. А вот front-end разработчику уйти в back-end будет сложнее, поскольку область его деятельности ограничена представлениями.

    Сделать HTML - это верстка, а не разработка, напрягать только ради этого разработчиков нет смысла, все равно что из пушки по воробьям стрелять, для этого есть верстальщики.
    Ответ написан
    Комментировать
  • Как лучше сделать в MVC подобее Url.Action в аттрибуте на server side?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Перенаправление можно выполнить примерно так:

    HttpContext.Current.Response.RedirectToRoute
    (
      new 
      { 
        controller = "Home", 
        action = "Index" 
      }
    );

    Лучше сделать вспомогательный класс и соответствующие методы для этого.

    Маршруты можно найти в System.Web.Routing.RouteTable.Routes.

    Данные текущего маршрута:

    var routeData = ((System.Web.Mvc.MvcHandler)HttpContext.Current.Handler).RequestContext.RouteData;

    Сделать экземпляр UrlHelper для контекста текущего запроса можно следующим образом:

    var urlHelper = new System.Web.Mvc.UrlHelper(HttpContext.Current.Request.RequestContext);
    var url = urlHelper.Action("Index", "Home");
    Ответ написан
    Комментировать
  • Как дебажить ajax ошибку?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Отключите обработку ошибок (в web.config и фильтры) и уведите в чем проблема.

    Метод научного тыка говорит, что проблема в кодированных данных (выделено жирным):

    +%26%23171%3B%D0%94%D0%A0%D0%A3%D0%93%D0%90+%D0%91%D0%9E%D0%A0%D0%9E%D0%94%D0%90%26%23187%3B

    %26%23171%3B => &#171;
    %26%23187%3B => &#187;
    Ответ написан
    2 комментария
  • Как обновить грид с другой страницы?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Если страница/окно с кнопкой создается с помощью JavaScript, то можно отправить прямой запрос родительскому окну (через window.parent). Если нет, то видимо с AJAX или задействовать SignalR, что окажется проще/ближе.
    Ответ написан
    Комментировать
  • Как скачать файл, в имени которого есть пробел?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    1. Попробуйте поместить имя файла в кавычки.

    2. Свойство FullName будет содержать полное имя файла, включая путь. В данном случае лучше использовать свойство Name (просто имя файла, без пути расположения).

    response.AddHeader("Content-Disposition", "attachment; filename='" + file.Name + "';");
    // или
    // response.AddHeader("Content-Disposition", "attachment; filename=\"" + file.Name + "\";");
    Ответ написан
    1 комментарий
  • Как учитывать часовой пояс пользователя на asp.net сайте?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    DateTime.Now - просто сделать поиск и замену (по файлам). Можно ограничить поиск типами файлов (.cs, .cshtml, .aspx, .ascx), чтобы не зацепить лишнего.

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

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

    В представлениях делать вывод даты и времени можно в какие-нибудь span-ы, например:
    <p>
      <span class="datetime">
        @Html.GetDateTime(DateTime.Now)
      </span>
    </p>
    <p>
      <span class="datetime" data-timestamp="@Html.GetTimestamp()">
        @Html.GetDateTime(DateTime.Now)
      </span>
    </p>
    
    <p>Метод <b>Html.GetDateTime</b> вполне может возвращать дату в тегах, тогда будет:</p>
    <p>@Html.GetDateTime(DateTime.Now)</p>
    <p>а на выходе может быть:<p>
    <p>
      <span class="datetime" data-timestamp="123">
        15.11.2016 21:11:21
      </span>
    </p>
    <p>
      <b>timestamp</b> - позволит обойти проблему с форматированием, 
      при расчете времени на стороне клиента.
    </p>

    С UTC будет проще работать, но вполне можно конвертировать время из времени сервера, главное не затеряться во времени, особенно между зимой и летом :-)

    С представлениями придется повозиться. Хотя если у вас имена полей, содержащие дату и время, более ли менее унифицированы, то проблем особых быть не должно.

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

    В коде (C#) с заменой особых проблем быть не должно.

    Замену в базе, при необходимости, можно автоматизировать. Единственное, если используется где-то GETDATE(), могут быть проблемы, тоже придется учитывать.

    Попробуйте для начала с представлениями (выводом) разобраться.
    А стоит проводить (в коде и базе) крупномасштабную замену времени сервера на UTC - время покажет.
    Ответ написан
    Комментировать
  • Какова правильная последовательность вызова методов Web Api?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Маркер доступа (access token) вдается один раз. На стороне клиента сохраняется в sessiongStorage либо в cookies. Используется при каждом запросе к API. Как правило, передавать маркер доступа лучше через заголовки. При использовании HTTPS заголовки будут зашифрованы.

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

    Срок действия маркер доступа может должен быть ограничен. Срок действия зависит от условий использования и необходимой степени безопасности. Например, если маркер привязан к IP, то срок действия вполне может быть продолжительным.

    На стороне сервера, для проверки доступа можно сделать отдельный фильтр, примерно как показано в коде ниже:

    class ApiAccess : AuthorizeAttribute
    {
    
      public override void OnAuthorization(HttpActionContext actionContext)
      {
        if (actionContext == null)
        {
          throw new ArgumentNullException("actionContext");
        }
    
        if (!this.IsAuthorized(actionContext))
        {
          return;
        }
      }
    
      protected override bool IsAuthorized(HttpActionContext actionContext)
      {
        bool isAuthroized = base.IsAuthorized(actionContext);
    
        // логика проверки доступа
    
        IEnumerable<string> authItems;
        if (actionContext.Request.Headers.TryGetValues("Authorization", out authItems))
        {
          var auth = authItems.First().Split(' ');
          var token = service.GetToken(auth.Last());
          // ...
        }
    
        return isAuthroized;
      }
    }

    Фильтр добавляется к контроллерам WebAPI, где необходима проверка доступа:

    [ApiAccess]
    public class FileServerController : ApiController
    {
    
       // ...
    
    }

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

    let url = '/методAPI';
    let data = {}; // параметры запроса
    let headers = {
      'Authorization': 'ANYNAMEHERE ' + sessionStorage.getItem('token')
    };
    
    $.ajax({
      cache: false,
      processData: false,
      type: 'POST',
      url: url,
      contentType: 'application/json',
      dataType: 'json',
      data: JSON.stringify(data),
      headers: headers,
      success: (result) => {
        // успех
      },
      error: (x, textStatus, errorThrown) => {
         // ошибка
    
         // на сервер можно сделать исключение для плохих маркеров доступа
         // и проверить, если responseText содержит данный тип исключения,
         // то требовать у пользователя повторную авторизацию
        if (x.responseText) {
           let exception = JSON.parse(x.responseText);
           // AccessDeniedException - тип исключения в WebAPI, 
           // (скорее всего полное имя типа придется указывать)
           if (exception.ExceptionType == 'AccessDeniedException') { 
              // ...
           }
        }
      }
    });

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

    Если API используется через отдельный (независимый от API) сайт, который авторизует пользователей, то пользователя можно не привлекать к процедуре получения нового маркера доступа, сайт это может сделать сам и передать новый маркер своему пользователю.

    Если API используется в браузере, как есть, то маркер доступа можно передавать в параметрах запроса. Однако это небезопасно, т.к. данные будут в открытом виде.
    Ответ написан
    6 комментариев
  • Как вывести данные из бд изменив снова сохранить?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    По указанной вами ссылке ведь есть готовый код:
    var grid, dialog;
    grid = $('#grid').grid({
        // ваш url, который будет передавать данные из вашей бд
        dataSource: '/Grid/GetPlayers',
        uiLibrary: 'bootstrap',
        // имена полей, которые следует выводить в таблице
        columns: [
            { field: 'ID', width: 32 },
            { field: 'Name', sortable: true },
            { field: 'PlaceOfBirth', title: 'Place Of Birth', sortable: true }
        ],
        pager: { limit: 5, sizes: [2, 5, 10, 20] }
    });

    Со стороны сервера можно сделать Action в обычном контроллере, который будет делать запрос к базе и возвращать данные в формате JSON. Но лучше использовать WebAPI, это будет проще.

    Для удаления и сохранения отдельные методы.

    Набросал простой пример: https://github.com/alekseynemiro/examples/tree/mas...
    Возможно для проверки работы примера потребуется переустановить пакеты. Для этого откройте меню Сервис => Диспетчер пакетов Nuget => Консоль диспетчера пакетов и введите следующую команду:

    Update-Package –reinstall

    В контроллере есть метод GetAccounts, который возвращает записи из базы:

    [HttpPost]
    public JsonResult GetAccounts(int page, int limit)
    {
      using (var context = new Database1Entities())
      {
        // получаем записи для указанной страницы
        var result = context.Account.OrderBy(
           row => row.AccountID
        ).Skip((page - 1) * limit).Take(limit).ToArray();
        int total = context.Account.Count();
    
        // возвращаем json
        return Json(new { records = result, total = total });
      }
    }

    В представлении, в коде инициализации jQuery Grid Bootstrap, указывается ссылка на этот метод:

    grid = $('#grid').grid({
      // ссылка на действие GetAccounts в контроллере Home
      // запрос выполняется методом POST
      dataSource: { url: '/Home/GetAccounts', method: 'POST' },
      uiLibrary: 'bootstrap',
      columns: [
        { field: 'AccountID', sortable: true },
        { field: 'FirstName', sortable: true },
        { field: 'LastName', sortable: true },
        { field: 'Company', sortable: true },
        { field: 'Position', sortable: true }
      ],
      pager: { limit: 2, sizes: [2, 5, 10, 20] }
    });
    Ответ написан
    Комментировать
  • ASP NET Как хранить данные внутри приложения?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    У приложения есть события запуска и завершения работы.

    Общие данные можно загружать при запуске приложения (Application_Start) или подгружать в статические классы в процессе работы приложения. Либо использовать HttpApplication, хотя смысла в этом особого нет.

    Важную информацию можно сохранить при завершении работы приложения (Application_End) в любое удобное хранилище (базы данных, файлы). Если данные очень важны, то лучше их периодически сохранять в процессе работы приложения (сделать таймер при инициализации приложения), т.к. рабочий процесс может быть завершен некорректно и событие Application_End может не возникнуть. Также следует учитывать вероятность неожиданного завершения работы приложения в момент сохранения важных данных (например, если рабочий процесс будет убит).

    Временные данные можно хранить в кэше.

    Небольшие объемы данных можно хранить в переменных, но лучше постараться держать такие данные в одном месте и не размазывать по всему приложению. Например, использование для этих целей классов контроллеров - не лучшая идея.
    Ответ написан
    Комментировать
  • Почему не срабатывает маршрут?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Проект точно был компилирован? Ошибок в процессе не возникало?

    Судя по маршруту, страница должна открываться по адресу:

    http://localhost:11733/meropriyatia

    Первый параметр - это имя сопоставляемого маршрута. Затем идет шаблон и значения по умолчанию.

    https://msdn.microsoft.com/ru-ru/library/dd504958(...

    Для адреса:

    http://localhost:11733/meropriyatiavnikolaeve

    маршрут должен быть таким:

    routes.MapRoute(
      "meropriyatiavnikolaeve", // имя
      "meropriyatiavnikolaeve", // шаблон
      new { controller = "Home", action = "meropriyatiavnikolaeve" }
    );
    Ответ написан
    5 комментариев
  • Как создавать и отображать элементы в одном представлении (представление строго типизировано)?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Нужно создать новую модель, которая будет включать в себя IEnumerable<City> и City.

    Примерно так:
    public class NewModel
    {
    
      public IEnumerable<City> CitiesList { get; set; }
    
      public City City { get; set; }
    
    }

    Использовать:
    @model NewModel

    @foreach(var city in Model.CitiesList)
    {
      // список
    }
    
    @Model.City.КакоетоСвойство

    Для удобства можно сделать частичные представления и передавать в них CitiesList и City. Первое представление должно ожидать IEnumerable<City>, второе - City. Т.е. будет примерно то, что у вас уже есть, просто обычные представления нужно сделать частичными (убрать все, что связано с шаблонизацией). Вывести эти представления в общем представлении, с передачей в их модель значений из модели общего представления (NewModel):

    @model NewMode
    
    @Html.Partial("CitiesList", Model.CitiesList)
    
    @Html.Partial("CityEditor", Model.City)

    Частичное представление CitiesList:

    @model IEnumerable<City>
    
    // ...

    Частичное представление CityEditor:

    @model City
    
    // ...
    Ответ написан
    2 комментария
  • Часто ли вы используете интерфейсы?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Интерфейсы использую, если это необходимо :-) Как я понимаю, что это необходимо, сказать сложно. Все зависит от задачи. Если рассматривать вопрос с позиции создания интерфейсов, то иногда это может быть очевидно, а иногда приходится подумать, стоит использовать интерфейсы или нет. Пихать их где попало - плохая идея.

    Готовые интерфейсы, да, часто используются. Самым популярным в .NET наверное будет IDisposable :-)

    Из общедоступных практических примеров использования собственных интерфейсов:

    • Простой интерфейс, описывающий всего один метод.

      interface ILoginForm
      {
      
        void WebDocumentLoaded(System.Windows.Forms.WebBrowser webBrowser, Uri url);
      
      }


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

      if (typeof(ILoginForm).IsAssignableFrom(this.GetType()))
      {
        this.CanLogin = false;
        Debug.WriteLine("ILoginForm", "LoginForm");
        ((ILoginForm)this).WebDocumentLoaded(this.webBrowser1, e.Url);
      }


    • Вот другой пример интерфейса.

      export interface ILocalization {
      
        Loading: string;
      
        LoadingFileContents: string;
      
        // ...
      }

      Файлы с ресурсами локализации реализуют этот интерфейс.

      export class RU implements ILocalization {
      
        public Loading: string = 'Загрузка...';
      
        public LoadingFileContents: string = 'Получение содержимого файла...';
      
        // ...
      }

      Это просто упрощает работу с кодом и больше ничего.

      6d84933efd7246e6adbeeed114ef8bbe.png

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


    • Еще пример интерфейса для вспомогательных классов работы с базами данных. Он просто описывает, каким должен быть класс.

      interface IDBClient
      {
          
        public function ExecuteNonQuery();
        public function ExecuteScalar();
        public function GetRow();
        // ...
      
      }

      А толку от этого никакого.

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

    • А вот тут я реализовал множество готовых интерфейсов, чтобы получить нужные свойства и поведение класса. Например, интерфейс IConvertible позволяет адекватно реагировать на Convert.To*. Интерфейс ISerializable позволяет описать правила сериализации экземпляра класса. И т.п.

      // реализация метода ToInt32
      public int ToInt32(IFormatProvider provider)
      {
        // если пусто, возвращаем ноль
        if (!this.HasValue) { return 0; }
        // если что-то есть, извлекаем числа и пробуем вернуть int32
        return Convert.ToInt32(OAuthUtility.GetNumber(this.Data));
      }


    • Вот пример, где можно было использовать интерфейсы, но я принял решение в пользу базовых классов (на раннем этапе). Некоторые поставщики OAuth позволяют обновлять маркер доступа и/или отзывать его, но не все. В базовом классе нижнего уровня для определениях этих особенностей я сделал два свойства, причем даже не абстрактных.

      public bool SupportRevokeToken { get; protected set; }
      public bool SupportRefreshToken { get; protected set; }

      Клиенты, которым нужны эти свойства, задают их в конструкторе, выглядит это так.

      public GoogleClient(string clientId, string clientSecret) : base(/*...*/)
      {
        // ...
        base.SupportRevokeToken = true;
        base.SupportRefreshToken = true;
      }

      Не очень красиво, но ошибкой это назвать нельзя. Если бы я использовал интерфейсы, то для отзыва и обновления маркера мне бы пришлось делать два разных интерфейса, в каждом по одному bool свойству, либо пустых (но с явно определенным свойством для других программистов все было бы очевидней; это тоже спорный вопрос). Можно было пойти дальше и измельчить на интерфейсы другие особенности, но это было бы плохим решением, т.к. программистам пришлось бы указывать портянку интерфейсов, а потом писать много кода. С базовыми классами код писать не нужно, даже не нужно вникать в то, как это работает; в большинстве случаев достаточно просто выполнить простую конфигурацию дочернего класса в конструкторе. Но я не могу утверждать на 100%, что это лучшее решение для данного случая.

    • Пример, где ReactJS и TypeScript заставляют клепать интерфейсы и я этому совсем не рад.

      export interface ILoginState {
      
        Username?: string;
        Password?: string;
      
        // ...
      
      }
      
      export default class Index extends Page<any, ILoginState> {
      
        constructor() {
      
          this.state = {
            Username: '',
            Password: ''
          };
      
        }
      
        // ...
      
      }

      Можно забить и использовать анонимные типы, но для порядка и удобства работы с кодом придется открывать собственный завод по производству интерфейсов :-)

    Ответ написан
    Комментировать
  • Изображение из базы данных в представление?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    При отправке формы, если пользователем не была отправлена другая картинка, вам придется получать предыдущую картинку из базы повторно и использовать при сохранении.

    В принципе, если у вас флажки небольшого размера, их можно и в представление интегрировать, в виде base64 в скрытом (hidden) поле :-) Тогда не придется делать запрос к базе. Но в реальных условиях это мало пригодно, поскольку есть риск подделки данных формы или потери актуальности данных, что нужно проверять, получая редактируемые данные из базы перед внесением в них изменений.
    Ответ написан
    Комментировать
  • Как корректно разделить проект на слои?

    AlekseyNemiro
    @AlekseyNemiro
    full-stack developer
    Берем проект, смотрим на класс модели, затем смотрим на слои. Берем лист бумаги и шариковую ручку с синими чернилами. Каллиграфическим почерком пишем:

    "Уважаемый В,

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

    С надеждой, _подпись_ /_расшифровка_/

    PS: Люблю сгущенку."


    ---

    Если серьезно, то ваш проект - вам виднее :-)

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

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

    Во всех случаях, без хорошей реализации, не имеет значения, был план или все придумалось и эволюционировало в процессе. Космический корабль все равно упадет в океан, не добравшись даже до верхних слоев атмосферы Земли, в лучшем случае безнадежно зависнет где-то там :-) Вариант с постепенным развитием будет лучшим, если смотреть на вопрос с позиции обучения и получения опыта. Стройте как вам удобно для достижения стоящих целей. Вписывать проект в неподходящий шаблон, в некоторых случаях, может быть даже хуже хаоса и неразберихи. Когда сделаете, посмотрите, в какой шаблон более ли менее вписались, сделаете выводы, насколько это хорошо или плохо для конкретной задачи и как это можно сделать лучше/проще/быстрее, как выписаться полностью (если этот вариант окажется приемлемым) и уменьшить количество коллизий. Если в процессе у вас возникают какие-то сомнения, то всегда можно задать узкий вопрос, наверняка вам ответят.

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