Как стать автором
Обновить

Комментарии 32

Хмм. Складывается у меня ощущение что вы "прогнали", потому как серверная часть ASP,NET html теги вообще никаким местом не трогает. И с чего она должна дублировать несколько раз некйй HTML тэг.

Проверю - отпишу подробнее. Но имхо - не соответствует действительности.
Читайте внимательнее текст, дублирование произойдет, когда на странице элемент управления используется несколько раз.
ну в общем в подавляющем большинстве страниц (по крайней мере которые я пишу) - есть 1-2 js файла, содержащие множество функций, которые используются на всех элементах.
Я же говорю - проверим.
06:41:25 127.0.0.1 GET /jstest/ 302
06:41:25 127.0.0.1 GET /jstest/Default.aspx 200
06:41:25 127.0.0.1 GET /jstest/requestTest.js 304

ЧТД.
1 запрос js файла.
на aspx странице - 14 записей вида
<asp:Button ID="Button1" runat="server" Text="Button1" OnClientClick="myalert()">
Я не знаю что и куда вы смотрите. Можете мне снижать карму, но вот реальный пример того, что я демонстрировал выше:
имеем страницу с двумя пользовательскими элементами, которые написаны "плохо" -

<form id="form1" runat="server">
<uc1:WebUserControl ID="WebUserControl1" runat="server" />
<br />
<br />
<uc1:WebUserControl ID="WebUserControl2" runat="server" />
</form>

имеем на выходе следующий html-текст:

<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>

</title></head>
<body>
<form name="form1" method="post" action="CtrlTest.aspx" id="form1">
<div>
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKMTEzMzI4MzgwMGRkt+GZS8kLa3l7NAdBn5i0fy5gG2A=" />
</div>


<script src="../js/some_js.js" type="text/javascript"></script>
<input type="submit" name="WebUserControl1$Button1" value="Button" onclick="OnClick();" id="WebUserControl1_Button1" />
<br />
<br />

<script src="../js/some_js.js" type="text/javascript"></script>
<input type="submit" name="WebUserControl2$Button1" value="Button" onclick="OnClick();" id="WebUserControl2_Button1" />

<div>

<input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEWAwKh+eS/BgKMgeP0CQL93ZHkAejhGwbwXP0EMYcwQT7Qge5qGAXz" />
</div></form>
</body>
</html>

Нетрудно заметить что объявление скрипта задваивается. Можете сами проверить.
да, извините, не обратил внимание на то, что речь шла о web user control.
Действительно "задваивает"...
Честно говоря не знал о таком решении..
Оно конечно менее читабельно, но более логично.
Кстати, Вы не хотите запостить это в .NET блог?
У меня кармы < 5 пока. Вроде бы я не могу постить в коллективные блоги. Или ошибаюсь?
Я, честно говоря, сам еще не разобрался как ту все эти Кармы и Хабросилы работают...
странно, вроде бы очень часто приходится вставлять ссылку на js, а решение не блещит минимализмом
это относится не к автору, а к Microsoft
Решение можно уменьшить на две строчки :) отказавшись от привязки к типу и обращаясь напрямую к Page.ClientScript.
Если серьезно, то лично у меня претензий к реализации нет. По моему проще и не сделаешь. Интересен другой факт. Подобного механизма в .net framework 1.0 просто не было. Похоже, что введение ClientScriptManager и множества прочих элементов касающихся клиентских скриптов, было произведено под давлением программистов, которым не смотря на всю силу asp.net все-таки нужны скрипты.
а нельзя хтмл-ый тег script не обрабатывать, а сделать аналогичный свой контрол ?
или я чего то не понимаю ?
Не понял вопрос, не совсем понял зачем делать еще контрол. На всякий случай объясню ситуацию: есть контрол, этакий черный ящик. Он зависит от внешнего файла js. То есть разработчик контрола должен быть уверен, что когда другой человек поместит контрол на страницу, то связка с js произойдет. Первый "плохой способ" тупо прописывал ссылку в теле контрола и на этом мысль заканчивалась. Второй способ - это грамотная проверка наличия ссылки в head родительской страницы и добавление ее в случае отсутствия. Больше делать ничего не нужно будет.
на самом деле если логично подумать - все правильно делал аспнет.
Чужой контрол - черный ящик. С какого ... кто-то должен оптимизировать содержимое черного ящика если на странице несколько экземпляров. А если там js файл определяется в результате декларативного параметра контрола илии что-то подобное?
Оптимизировать чужие контролы - нарушение парадигмы инкапсуляции.

Проблема, имхо, состоит больше в том, что так неаккуратно писать контролы не стоит. Можно же и в сам контрол нашарашить такой логики что 2-3 контрола на странице обрушат сервер.

PS: Правда, чтобы писать аккуратно - надо такие вещи знать - чем чаще с сталкиваешься с такими подставами, тем более опытным становишься. Правда задумываешься - а так ли "все хорошо в датском королевстве", ибо некоторые решения пугают своей "катастрофичностью".
Не знаю насчёт 1.0, но в 1.1 подобные методы были реализованы прямо в классе Page. А во втором фреймворке эти методы инкапсулировали в классе ClientScriptManager.
Наконец-то появился на любимом хабре блог про .Net всегда его ждали :)
и никто не догадывался открыть? :)
всегда куча дел :) теперь подготовим пост и выложим в скором времени :)
Ещё один вариант - написать свой элемент управления, чтобы задавать скрипты в декларативном виде. Что-то такое:


public sealed class ScriptIncluder : Control
{
private Dictionary _scripts
{
get
{
return (Dictionary )(Context.Items[typeof(ScriptIncluder)] ?? (Context.Items[typeof(ScriptIncluder)] = new Dictionary ()));
}
}

private string _path;

public string Path
{
get { return _path; }
set
{
_path = Page.Server.MapPath(value);
Dictionary dict = _scripts;
if (!dict.ContainsKey(_path))
dict.Add(_path, false);
}
}

protected override void Render(HtmlTextWriter writer)
{
if (String.IsNullOrEmpty(_path))
return;

Dictionary dict = _scripts;
bool alreadyRendered = dict[_path];
if (!alreadyRendered)
{
writer.Write(@" ", _path);
dict[_path] = true;
}
}
}


Использование:

<mycontrols:ScriptIncluder runat="server" Path="~/js/my_script.js" />
Создать контрол на базе моего примера - дело техники. Что касается вашего класса: если вы используете Page, то зачем создавать свой dictionary, свою логику, а не использовать Page.ClientScript и методы которые я привел выше? К тому же, проверьте, что вам вернет Page.Server.MapPath(value)? Используйте ResolveClientUrl. Ну и если мне не изменяют мои глаза этот пример повыводит одни абсолютные имена файлов на сервере, вместо того, чтобы зарегистрировать в head нормальные link на js файлы.
Ваша правда, MapPath - это я сгоряча.

Лучше, конечно было сделать так:


public sealed class ScriptIncluder : Control
{
private string _path;

public string Path
{
get { return _path; }
set
{
_path = ResolveClientUrl(value);
if (!Page.ClientScript.IsClientScriptIncludeRegistered(Page.GetType(), _path))
Page.ClientScript.RegisterClientScriptInclude(Page.GetType(), _path, _path);
}
}
}


Мне кажется, что суть в том, что надо стремиться к максимуму декларативности, поэтому я и решил написать по-быстому соответствующий элемент управления.
Беда такого подхода в том, что в итоге мы делаем контрол для включения в другой контрол. Не знаю, насколько это грамотно, но мне лично не нравится. Еще, например в случае установки свойства visible = false для нашего контрола ваш вариант все равно добавит ссылку на js в код. Для того, чтобы отследить эту ситуацию в ваш контрол надо добавлять логику по включению/выключению. Потом, есть смысл отслеживать postback'и. Да еще много чего может понадобится. В итоге, придется сделать ScriptIncluder эдаким ящиком с кучей рычагов, вместо того, чтобы гибко вызывать две функции там где нужно... Я вполне понимаю, что смысл в создании ScriptIncluder есть, но пока я придерживаюсь своего варианта.
Такой подход реализован, например в Microsoft ASP.NET AJAX. Там у элемента управления ScriptManager есть ScriptManagerProxy, с помощью которого можно устанавливать декларативно свойства у ScriptManager на страницах и пользовательских элементах управления (если, например, сам ScriptManager объявлен в Master Page).

P.S. А вообще, я сам все скрипты содержу в одном файле (типа "оптимизация"), покуда это возможно по объёмам :)
А как же наследование от IScriptControl?

[assembly: WebResource(MyControl.ScriptName, "text/javascript")]
public partial class MyControl : UserControl, IScriptControl {
internal const string ScriptName= "namespace.MyControl.js";
...

protected override void OnPreRender(EventArgs e) {
if (!DesignMode) {
ScriptManager.GetCurrent(Page).RegisterScriptControl(this);
}
}

protected override void Render(HtmlTextWriter writer) {
if (!DesignMode) {
ScriptManager.GetCurrent(Page).RegisterScriptDescriptors(this);
}
}

public IEnumerable GetScriptDescriptors() {
ScriptControlDescriptor descriptor = new ScriptControlDescriptor(ClientControlType, ClientID);

передаём нужные параметры скрипту

yield return descriptor;
}

public IEnumerable GetScriptReferences() {
List references = new List ();
references.Add(new ScriptReference(ScriptName, typeof(MyControl).Assembly.FullName));
return references;
}

}
А какая выгода от всех этих телодвижений, если скрипт и так можно добавить в две строчки? Я не шучу, мне правда интересно.
Сори, не упомянул что IScriptControl используется в asp.net AJAX.
По мне, делать emmbeded скрипты, да и CSS тоже, для отдельных UserControls и CustomControls более правильней, ну или хотя бы чище, так как потом всё сидит в одном dll'льчике а не разбросано по куче файлов. Но это моё скромное мнение.
Наверно все таки стоит обернуть регистрацию в !PostBack и собственно добавить метод к контролу, т.е.:
this.Button1.Attributes.Add("onClick", "OnClick()");
Читайте внимательнее: "Этот код следует расположить в методе Page_Load или там где следует из логики вашего элемента управления.". Из этого следует, что я привел только кусок функционала, использование его уже работа конкретного программиста и ситуации.
Про постбэк согласен
Хм, как-то сложновато все это. Для единичной регистрации скрипта контрола достаточно сделать следующее:

string jsCode = String.Format(" selectId= '{0}' ", DropDownList1.ClientID);
Page.ClientScript.RegisterStartupScript(typeof(Page), "clientId_including", jsCode);
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории