Ответы пользователя admax - CodeHelper

admax

admax
Карма 2924
Имя Евгений
Возраст Неизвестно
Сайт Неизвестно
Twitter @codehelper_ru
Участвует в проекте 1118 дн., 14 час., 4 мин.
Последняя активность 419 дн., 6 час., 34 мин. назад
О себе ничего не указано

Ответы

Новые Лучшие
1
2
3
4
5
6
...
13
Перейти к вопросу →

Если нужно выводить количество или имена авторизованных пользователей, то нужно использовать статичные методы класса Membership:

// Возвращает количество авторизаванных пользователей
Membership.GetNumberOfUsersOnline(); 
// Возвращает коллекцию авторизованных пользователей
Membership.GetAllUsers();

Для функции Membership.GetAllUsers есть перегруженный вариант, поддерживающий постраничный вывод.

Перейти к вопросу →

Если нужно показать число активных пользователей на сайте (в том числе и не авторизованных), то можно хранить число пользователей в объекте глобальном Application и подписаться на события начала и окончания сессии в Global.asax:

void Application_Start(object sender, EventArgs e)
{
    Application["UsersOnline"] = 0;             1
}

void Session_Start(object sender, EventArgs e)
{
    var onlineUsersCount =                      2
        Convert.ToInt16(Application["UsersOnline"]);
    Application["UsersOnline"] = (onlineUsersCount + 1).ToString();
}

void Session_End(object sender, EventArgs e)
{
    var onlineUsersCount =                      3
        Convert.ToInt16(Application["UsersOnline"]);
    Application["UsersOnline"] = (onlineUsersCount - 1).ToString();
}
  • 1 — при запуске приложения устанавливаем счетчик в ноль.
  • 2 — при старте новой сессии прибавляем к счетчику единицу.
  • 3 — при окончании сессии отнимаем от счетчика единицу.

На странице отображаем значение счетчика:

private void Page_Load(object sender, System.EventArgs e) 
{ 
   onlineUsersCountLabel.Text = Application["OnlineUsers"];
}
Перейти к вопросу →

Есть два варианта:

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

routes.MapRoute("Default",
    "product/{action}/{id}",
    new
    {
        controller = "Product",
        action = "Index",
        id = ""
    });

Если переписать этот код, дабавив аргумен с ограничениями, то на контроллер ProductController будут перенаправляться не все подряд, а только определенные методы (index и details):

routes.MapRoute("Default",
    "product/{action}/{id}",
    new
    {
        controller = "Product",
        action = "Index",
        id = ""
    },
    new {action = "index|details"});

Тогда запрос на несуществующий action (например, /product/foo) не будет перехвачен этим правилом и поиск подходящего для него контроллера продолжется. Чтобы этот поиск завершился, нужно добавить к таблице роутинга правило, перехватывающее любой необработанный запрос. Так как правила обрабатываются по порядку, то если catch-all-роут добавлен в таблицу последним, то он будет перехватывать все необработанные запросу но не затронет ни одного запроса, относящегося к существующему контроллеру/экшену. Итак, последним правилом добавляем:

routes.MapRoute(
    "CatchAll", 
    "{*url}",
    new
    {
        controller = "Error", 
        action = "NotFound"
    });

Это правило перенаправляет все запросы на экшен NotFound контроллера ErrorController. Соответствующий метод может выглядить так:

public class ErrorController : Controller
{
    public ActionResult NotFound()
    {
        controller.Response.StatusCode = 404;             1
        return new ViewResult { ViewName = "Error404" };  2
    }
}

Здесь:

1 — устанавливается статус (404 Not Found);

2 — возвращается вид с кастомизированной страницей 404.

2) Переопределить метод HandleUnknownAction контроллера и выполнить в теле метода необходимые действия — вручную собрать и выполнить ActionResult, возвращающий страницу 404:

protected override void HandleUnknownAction(string actionName)
{
    controller.Response.StatusCode = 404;
    var result = new ViewResult { ViewName = "Error404" };
    result.ExecuteResult(ControllerContext);
}
Перейти к вопросу →

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

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

Возвращать null если:

  1. Вызывающий код допускает возвращения null вместо объекта и корректно обрабатывает такой сценарий.

Рассмотрим это все на примере. Допустим, есть метод

User FindUserByName(string name) {},

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

Вариант №2 Вернуть пустой объект (0, String.Empty, пустой список и т.д.) кажется мало применимым на практике из-за его неинформативности.

Допустим есть метод

string GetUserName(long userId);

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

В любом случае, главное — придерживаться стандарта именования методов и поведения в исключительных ситуациях, чтобы все методы вели себя единообразно. Можно, например, называть методы, возвращающие null GetSomeObject(), а методы, выкидывающие исключения — GetRequiredObject() и тп.

Перейти к вопросу →

В конфигуровочных файлах Spring.NET можно использовать плейсхолдеры, которые во время инициализации будут заменены значениями из конфигуровочного файла. Например, в App.config/Web.config указанно:

<appSettings>
  <add key="SomeProperty" value="10"/>
</appSettings>

Далее, в Objects.xml нужно создать объект, который будет заменять плейсхолдеры реальными значениями:

<object name="appConfigPropertyHolder" type="Spring.Objects.Factory.Config.PropertyPlaceholderConfigurer, Spring.Core">
    <property name="IgnoreUnresolvablePlaceholders" value="true" />
    <property name="configSections">
        <value>appSettings</value>
    </property>
</object>

Теперь в любом месте XML конфигурации можно использовать placeholder ${SomeProperty}. Более того, у объекта PropertyPlaceholderConfigurer есть свойство EnvironmentVariableMode, с помощью которого можно указать, какие действия предпринимать если есть переменная среды с именем аналогичным плейсхолдеру. Возможные значения:

  • Fallback — значения из .config файла перекрывают значения переменных среды;
  • Never — переменные среды игнорируются;
  • Override - переменные среды перекрывают значения из .config-файла.

Итак, для замены всех свойств переменными среды нужно приведенный выше код заменить на следующий:

<object name="appConfigPropertyHolder" type="Spring.Objects.Factory.Config.PropertyPlaceholderConfigurer, Spring.Core">
  <property name="IgnoreUnresolvablePlaceholders" value="true" />
  <property name="EnvironmentVariableMode" value="Override" />
  <property name="configSections">
    <value>appSettings</value>
  </property>
</object>
Перейти к вопросу →

Есть родственные вопросы насчет пакетирования и организации структуры проекта.

Один из принципов пакетирования гласит, что «должна быть толко одна причина для изменения пакета». То есть любое изменение должно быть локализовано и должно затрагивать только определенные сборки. Вариант, при котором есть три проекта Models, Views, Controllers не отвечает этому принципу, потому что любое изменение будет сквозным — оно затронет и модель и виды и контроллеры. Если же разбивать на проекты по логическим частям, то для каждой такой части можно проводить локальные изменения не затрагивая другие.

Вообще задача пакетирования достаточно запутанная, и, покрайней мере в .NET, каждый организует структуру проекта исходя из личных предпочтений, а не best practices.

Перейти к вопросу →

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

Метод RouteLink использует имя объекта route для поиска в таблице. Соответственно, порядок добавления записей в таблицу роутинга не имеет значения, и изменения/добавление роутов не нарушит работу RouteLink. Также, RouteLink предоставляет бОльшую точность в выборе роута, что особенно полезно когда ActionLink может вызвать некоторую неоднозначность. Например, может быть несколько роутов для одного контроллера и экшена. Тогда ActionLink будет всегда возвращать первый роут, а с помощью RouteLink можно указать конкретный route.

Перейти к вопросу →

Самый очевидный способ — использование перенаправлений (redirect) всех некорректных адресов на один корректный. Этот способ не только устранит дублирование контента, но и поможет перенести «ссылочную ценность» на одну страницу. Для перенаправления используется статусы HTTP 30x. В нашем случае идеально подойдет статус "301 Moved Permanently", сообщающий клиенту (поисковику, браузеру и т.п.), что запрашиваемый ресурс следует загрузить с другого адреса.

Вообще, перенаправления (redirect) можно настроить на уровне IIS, используя решение типа ISAPI_Rewrite. Также инструменты подмены URL (UrlRewriter.NET, UrlRewriting.NET) могут выполнять перенаправления. Но в нашем случае такие способы не годятся, потому что модуль, осуществляющий redirect должен иметь связь с базой данных или другим хранилищем сущностей. Иначе адрес вида http://somesite.ru/category/13/ не удастся преобразовать к http://somesite.ru/category/13/текстовое-описание-категории. Итак, единственное решение — вручную написать код, осуществляющий redirect в случае отличия текущего URL от корректного. Такой код обычно жестко связан с конкретной предметной областью, поэтому сложно привести общий пример. Для ASP.NET код может выглядеть так:

public static class UrlHelper
{
    public static void CheckUrlcanonical()
    {
        HttpContext context = HttpContext.Current;
        string currentLocation = context.Request.Path.ToLower();
        string canonicalLocation = GetCanonicalLocation()      1;
        if (currentLocation != canonicalLocation)              2
        {
            context.Response.Status = "301 Moved Permanently"; 3
            context.Response.AddHeader("Location", canonicalLocation)
        }
    }
}

1 — метод GetCanonicalLocation возвращает канонический адрес для запрашиваемой страницы, возможно, обращаясь к слою доступа к данным или сервисам.

2 — проверяем, является ли текущей адрес каноническим

3 — осуществляем redirect — устанавливаем статус 301 и путь для перенаправления.

Метод UrlHelper.GetCanonicalLocation() можно вызывать в обработчике Page_Load() соответствующей страницы или глобально для всего приложения — в Global.asax (Application_BeginRequest())

Перейти к вопросу →

Можно воспользоваться элементом link с rel="canonical" для указания канонического (предпочитаемого) адреса страницы:

<link rel="canonical" href="http://somesite.ru/category/13/текстовое-описание-категории">

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

Такой подход отлично сработает для всех (или большинства) заморских поисковиков, но наши поисковики (в частности Яндекс) не воспринимают rel="canonical".

Перейти к вопросу →

Парадигма ASP.NET не проектировалась для поддержки модульного тестирования. Так, типичный модуль или обработчик работает с экземплярами классов HttpContext, HttpResponse, HttpRequest (и многими другими), которые являются синтглтонами. Соответственно тестировать такой код непосредственно практически невозможно. Для решения задачи тестирования создаются обертки над указанными синглтонами и вся логика строится так, чтобы работать с экземплярами интерфейсов, а не со статичными объектами. К счастью создавать библиотеки-обертки самому не нужно т.к. есть готовые варианты.

Вариант 1: Phil Haack описывает такую библиотеку-обертку в статье IHttpContext And Other Interfaces For Your Duck Typing Benefit

Вариант 2: В пространстве имен System.Web.Abstractions есть реализованные обертки над объектами HttpContext, HttpResponse, HttpRequest и т.п. Это классы HttpContextWrapper, HttpResponseWrapper и HttpRequestWrapper соответственно. Эти классы используются внутри ASP.NET MVC, именно для поддержки возможности тестирования. Есть интересная техника тестирования модулей и хендлеров, применяемая в частности в CMS Kigg. Суть подхода:

Создаются базовые классы, в которых объявлены абстрактные методы, использующие обертки вместо реальных http-related объектов:

public abstract class BaseHttpModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.BeginRequest += (sender, e) => OnBeginRequest(new HttpContextWrapper(((HttpApplication) sender).Context));
        context.Error += (sender, e) => OnError(new HttpContextWrapper(((HttpApplication) sender).Context));
        context.EndRequest += (sender, e) => OnEndRequest(new HttpContextWrapper(((HttpApplication) sender).Context));
    }

    public void Dispose() {}

    public virtual void OnBeginRequest(HttpContextBase context){}

    public virtual void OnError(HttpContextBase context){}

    public virtual void OnEndRequest(HttpContextBase context){}
}

public abstract class BaseHttpHandler : IHttpHandler
{
    public virtual bool IsReusable
    {
        get
        {
            return false;
        }
    }

    public void ProcessRequest(HttpContext context)
    {
        ProcessRequest(new HttpContextWrapper(context));
    }

    public abstract void ProcessRequest(HttpContextBase context);
}

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


1
2
3
4
5
6
...
13
v1.7.123.556
© 2009—2010 CodeHelper FAQ | О сайте | Обратная связь | История изменений | Статьи
Creative Commons LicenseМатериалы сайта распространяются под лицензией Creative Commons Attribution-Share Alike 3.0 Unported.