2 // System.Web.UI.ClientScriptManager.cs
5 // Duncan Mak (duncan@ximian.com)
6 // Gonzalo Paniagua (gonzalo@ximian.com)
7 // Andreas Nahr (ClassDevelopment@A-SoftTech.com)
8 // Lluis Sanchez (lluis@novell.com)
10 // (C) 2002,2003 Ximian, Inc. (http://www.ximian.com)
11 // (c) 2003-2010 Novell, Inc. (http://www.novell.com)
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 using System.Collections;
37 using System.Collections.Generic;
39 using System.Collections.Specialized;
40 using System.Web.Util;
41 using System.Globalization;
43 namespace System.Web.UI
45 public sealed partial class ClientScriptManager
47 internal const string EventStateFieldName = "__EVENTVALIDATION";
49 Hashtable registeredArrayDeclares;
50 ScriptEntry clientScriptBlocks;
51 ScriptEntry startupScriptBlocks;
52 internal Hashtable hiddenFields;
53 ScriptEntry submitStatements;
55 int [] eventValidationValues;
56 int eventValidationPos = 0;
57 Hashtable expandoAttributes;
58 bool _hasRegisteredForEventValidationOnCallback;
60 bool _initCallBackRegistered;
61 bool _webFormClientScriptRendered;
62 bool _webFormClientScriptRequired;
64 internal bool ScriptsPresent {
66 return _webFormClientScriptRequired ||
67 _initCallBackRegistered ||
68 _hasRegisteredForEventValidationOnCallback ||
69 clientScriptBlocks != null ||
70 startupScriptBlocks != null ||
71 submitStatements != null ||
72 registeredArrayDeclares != null ||
73 expandoAttributes != null;
77 internal ClientScriptManager (Page page)
82 public string GetPostBackClientHyperlink (Control control, string argument)
84 return "javascript:" + GetPostBackEventReference (control, argument);
87 public string GetPostBackClientHyperlink (Control control, string argument, bool registerForEventValidation)
89 if (registerForEventValidation)
90 RegisterForEventValidation (control.UniqueID, argument);
91 return "javascript:" + GetPostBackEventReference (control, argument);
94 public string GetPostBackEventReference (Control control, string argument)
97 throw new ArgumentNullException ("control");
99 page.RequiresPostBackScript ();
101 return page.theForm + ".__doPostBack('" + control.UniqueID + "','" + argument + "')";
103 return "__doPostBack('" + control.UniqueID + "','" + argument + "')";
106 public string GetPostBackEventReference (Control control, string argument, bool registerForEventValidation)
109 throw new ArgumentNullException ("control");
111 if (registerForEventValidation)
112 RegisterForEventValidation (control.UniqueID, argument);
113 return GetPostBackEventReference (control, argument);
116 public string GetPostBackEventReference (PostBackOptions options, bool registerForEventValidation)
119 throw new ArgumentNullException ("options");
120 if (registerForEventValidation)
121 RegisterForEventValidation (options);
122 return GetPostBackEventReference (options);
125 public string GetPostBackEventReference (PostBackOptions options)
128 throw new ArgumentNullException ("options");
130 if (options.ActionUrl == null && options.ValidationGroup == null && !options.TrackFocus &&
131 !options.AutoPostBack && !options.PerformValidation)
133 if (!options.ClientSubmit)
136 if (options.RequiresJavaScriptProtocol)
137 return GetPostBackClientHyperlink (options.TargetControl, options.Argument);
139 return GetPostBackEventReference (options.TargetControl, options.Argument);
142 RegisterWebFormClientScript ();
144 string actionUrl = options.ActionUrl;
145 if (actionUrl != null)
146 RegisterHiddenField (Page.PreviousPageID, page.Request.FilePath);
148 if(options.TrackFocus)
149 RegisterHiddenField (Page.LastFocusID, String.Empty);
151 string prefix = options.RequiresJavaScriptProtocol ? "javascript:" : String.Empty;
152 if (page.IsMultiForm)
153 prefix += page.theForm + ".";
155 return prefix + "WebForm_DoPostback(" +
156 ClientScriptManager.GetScriptLiteral (options.TargetControl.UniqueID) + "," +
157 ClientScriptManager.GetScriptLiteral (options.Argument) + "," +
158 ClientScriptManager.GetScriptLiteral (actionUrl) + "," +
159 ClientScriptManager.GetScriptLiteral (options.AutoPostBack) + "," +
160 ClientScriptManager.GetScriptLiteral (options.PerformValidation) + "," +
161 ClientScriptManager.GetScriptLiteral (options.TrackFocus) + "," +
162 ClientScriptManager.GetScriptLiteral (options.ClientSubmit) + "," +
163 ClientScriptManager.GetScriptLiteral (options.ValidationGroup) + ")";
166 internal void RegisterWebFormClientScript ()
168 if (_webFormClientScriptRequired)
171 page.RequiresPostBackScript ();
172 _webFormClientScriptRequired = true;
175 internal void WriteWebFormClientScript (HtmlTextWriter writer) {
176 if (!_webFormClientScriptRendered && _webFormClientScriptRequired) {
178 WriteClientScriptInclude (writer, GetWebResourceUrl (typeof (Page), "webform.js"), typeof (Page), "webform.js");
179 WriteBeginScriptBlock (writer);
180 writer.WriteLine ("WebForm_Initialize({0});", page.IsMultiForm ? page.theForm : "window");
181 WriteEndScriptBlock (writer);
182 _webFormClientScriptRendered = true;
186 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context)
188 return GetCallbackEventReference (control, argument, clientCallback, context, null, false);
191 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context, bool useAsync)
193 return GetCallbackEventReference (control, argument, clientCallback, context, null, useAsync);
196 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context, string clientErrorCallback, bool useAsync)
199 throw new ArgumentNullException ("control");
200 if(!(control is ICallbackEventHandler))
201 throw new InvalidOperationException ("The control must implement the ICallbackEventHandler interface and provide a RaiseCallbackEvent method.");
203 return GetCallbackEventReference ("'" + control.UniqueID + "'", argument, clientCallback, context, clientErrorCallback, useAsync);
206 public string GetCallbackEventReference (string target, string argument, string clientCallback, string context, string clientErrorCallback, bool useAsync)
208 RegisterWebFormClientScript ();
210 if (!_initCallBackRegistered) {
211 _initCallBackRegistered = true;
212 RegisterStartupScript (typeof (Page), "WebForm_InitCallback", page.WebFormScriptReference + ".WebForm_InitCallback();", true);
214 return page.WebFormScriptReference + ".WebForm_DoCallback(" +
216 (argument ?? "null") + "," +
217 clientCallback + "," +
218 (context ?? "null") + "," +
219 (clientErrorCallback ?? "null") + "," +
220 (useAsync ? "true" : "false") + ")";
223 public string GetWebResourceUrl(Type type, string resourceName)
226 throw new ArgumentNullException ("type");
228 if (resourceName == null || resourceName.Length == 0)
229 throw new ArgumentNullException ("type");
231 return System.Web.Handlers.AssemblyResourceLoader.GetResourceUrl (type, resourceName);
235 public bool IsClientScriptBlockRegistered (string key)
237 return IsScriptRegistered (clientScriptBlocks, GetType(), key);
240 public bool IsClientScriptBlockRegistered (Type type, string key)
242 return IsScriptRegistered (clientScriptBlocks, type, key);
245 public bool IsStartupScriptRegistered (string key)
247 return IsScriptRegistered (startupScriptBlocks, GetType(), key);
250 public bool IsStartupScriptRegistered (Type type, string key)
252 return IsScriptRegistered (startupScriptBlocks, type, key);
255 public bool IsOnSubmitStatementRegistered (string key)
257 return IsScriptRegistered (submitStatements, GetType(), key);
260 public bool IsOnSubmitStatementRegistered (Type type, string key)
262 return IsScriptRegistered (submitStatements, type, key);
265 public bool IsClientScriptIncludeRegistered (string key)
267 return IsClientScriptIncludeRegistered (GetType (), key);
270 public bool IsClientScriptIncludeRegistered (Type type, string key)
272 return IsScriptRegistered (clientScriptBlocks, type, "include-" + key);
275 bool IsScriptRegistered (ScriptEntry scriptList, Type type, string key)
277 while (scriptList != null) {
278 if (scriptList.Type == type && scriptList.Key == key)
280 scriptList = scriptList.Next;
285 public void RegisterArrayDeclaration (string arrayName, string arrayValue)
287 if (registeredArrayDeclares == null)
288 registeredArrayDeclares = new Hashtable();
290 if (!registeredArrayDeclares.ContainsKey (arrayName))
291 registeredArrayDeclares.Add (arrayName, new ArrayList());
293 ((ArrayList) registeredArrayDeclares[arrayName]).Add(arrayValue);
294 page.RequiresFormScriptDeclaration ();
297 void RegisterScript (ref ScriptEntry scriptList, Type type, string key, string script, bool addScriptTags)
299 RegisterScript (ref scriptList, type, key, script, addScriptTags ? ScriptEntryFormat.AddScriptTag : ScriptEntryFormat.None);
302 void RegisterScript (ref ScriptEntry scriptList, Type type, string key, string script, ScriptEntryFormat format)
304 ScriptEntry last = null;
305 ScriptEntry entry = scriptList;
307 while (entry != null) {
308 if (entry.Type == type && entry.Key == key)
314 entry = new ScriptEntry (type, key, script, format);
316 if (last != null) last.Next = entry;
317 else scriptList = entry;
320 internal void RegisterClientScriptBlock (string key, string script)
322 RegisterScript (ref clientScriptBlocks, GetType(), key, script, false);
325 public void RegisterClientScriptBlock (Type type, string key, string script)
327 RegisterClientScriptBlock (type, key, script, false);
330 public void RegisterClientScriptBlock (Type type, string key, string script, bool addScriptTags)
333 throw new ArgumentNullException ("type");
335 RegisterScript (ref clientScriptBlocks, type, key, script, addScriptTags);
338 public void RegisterHiddenField (string hiddenFieldName, string hiddenFieldInitialValue)
340 if (hiddenFields == null)
341 hiddenFields = new Hashtable ();
343 if (!hiddenFields.ContainsKey (hiddenFieldName))
344 hiddenFields.Add (hiddenFieldName, hiddenFieldInitialValue);
347 internal void RegisterOnSubmitStatement (string key, string script)
349 RegisterScript (ref submitStatements, GetType (), key, script, false);
352 public void RegisterOnSubmitStatement (Type type, string key, string script)
355 throw new ArgumentNullException ("type");
357 RegisterScript (ref submitStatements, type, key, script, false);
360 internal void RegisterStartupScript (string key, string script)
362 RegisterScript (ref startupScriptBlocks, GetType(), key, script, false);
365 public void RegisterStartupScript (Type type, string key, string script)
367 RegisterStartupScript (type, key, script, false);
370 public void RegisterStartupScript (Type type, string key, string script, bool addScriptTags)
373 throw new ArgumentNullException ("type");
375 RegisterScript (ref startupScriptBlocks, type, key, script, addScriptTags);
378 public void RegisterClientScriptInclude (string key, string url)
380 RegisterClientScriptInclude (GetType (), key, url);
383 public void RegisterClientScriptInclude (Type type, string key, string url)
386 throw new ArgumentNullException ("type");
387 if (url == null || url.Length == 0)
388 throw new ArgumentException ("url");
390 RegisterScript (ref clientScriptBlocks, type, "include-" + key, url, ScriptEntryFormat.Include);
393 public void RegisterClientScriptResource (Type type, string resourceName)
395 RegisterScript (ref clientScriptBlocks, type, "resource-" + resourceName, GetWebResourceUrl (type, resourceName), ScriptEntryFormat.Include);
398 public void RegisterExpandoAttribute (string controlId, string attributeName, string attributeValue)
400 RegisterExpandoAttribute (controlId, attributeName, attributeValue, true);
403 public void RegisterExpandoAttribute (string controlId, string attributeName, string attributeValue, bool encode)
405 if (controlId == null)
406 throw new ArgumentNullException ("controlId");
408 if (attributeName == null)
409 throw new ArgumentNullException ("attributeName");
411 if (expandoAttributes == null)
412 expandoAttributes = new Hashtable ();
414 ListDictionary list = (ListDictionary)expandoAttributes [controlId];
416 list = new ListDictionary ();
417 expandoAttributes [controlId] = list;
420 list.Add (attributeName, encode ? StrUtils.EscapeQuotesAndBackslashes (attributeValue) : attributeValue);
423 void EnsureEventValidationArray ()
425 if (eventValidationValues == null || eventValidationValues.Length == 0)
426 eventValidationValues = new int [64];
428 int len = eventValidationValues.Length;
430 if (eventValidationPos >= len) {
431 int [] tmp = new int [len * 2];
432 Array.Copy (eventValidationValues, tmp, len);
433 eventValidationValues = tmp;
437 internal void ResetEventValidationState ()
439 _pageInRender = true;
440 eventValidationPos = 0;
443 // Implemented following the description in http://odetocode.com/Blogs/scott/archive/2006/03/20/3145.aspx
444 int CalculateEventHash (string uniqueId, string argument)
446 int uniqueIdHash = uniqueId.GetHashCode ();
447 int argumentHash = String.IsNullOrEmpty (argument) ? 0 : argument.GetHashCode ();
448 return (uniqueIdHash ^ argumentHash);
451 public void RegisterForEventValidation (PostBackOptions options)
453 // MS.NET does not check for options == null, so we won't too...
454 RegisterForEventValidation (options.TargetControl.UniqueID, options.Argument);
457 public void RegisterForEventValidation (string uniqueId)
459 RegisterForEventValidation (uniqueId, null);
462 public void RegisterForEventValidation (string uniqueId, string argument)
464 if (!page.EnableEventValidation)
466 if (uniqueId == null || uniqueId.Length == 0)
469 _hasRegisteredForEventValidationOnCallback = true;
470 else if (!_pageInRender)
471 throw new InvalidOperationException ("RegisterForEventValidation may only be called from the Render method");
473 EnsureEventValidationArray ();
475 int hash = CalculateEventHash (uniqueId, argument);
476 for (int i = 0; i < eventValidationPos; i++)
477 if (eventValidationValues [i] == hash)
479 eventValidationValues [eventValidationPos++] = hash;
482 public void ValidateEvent (string uniqueId)
484 ValidateEvent (uniqueId, null);
487 ArgumentException InvalidPostBackException ()
489 return new ArgumentException ("Invalid postback or callback argument. Event validation is enabled using <pages enableEventValidation=\"true\"/> in configuration or <%@ Page EnableEventValidation=\"true\" %> in a page. For security purposes, this feature verifies that arguments to postback or callback events originate from the server control that originally rendered them. If the data is valid and expected, use the ClientScriptManager.RegisterForEventValidation method in order to register the postback or callback data for validation.");
492 public void ValidateEvent (string uniqueId, string argument)
494 if (uniqueId == null || uniqueId.Length == 0)
495 throw new ArgumentException ("must not be null or empty", "uniqueId");
496 if (!page.EnableEventValidation)
498 if (eventValidationValues == null)
499 throw InvalidPostBackException ();
501 int hash = CalculateEventHash (uniqueId, argument);
502 for (int i = 0; i < eventValidationValues.Length; i++)
503 if (eventValidationValues [i] == hash)
506 throw InvalidPostBackException ();
509 void WriteScripts (HtmlTextWriter writer, ScriptEntry scriptList)
511 if (scriptList == null)
516 while (scriptList != null) {
517 switch (scriptList.Format) {
518 case ScriptEntryFormat.AddScriptTag:
519 EnsureBeginScriptBlock (writer);
520 writer.Write (scriptList.Script);
522 case ScriptEntryFormat.Include:
523 EnsureEndScriptBlock (writer);
524 WriteClientScriptInclude (writer, scriptList.Script, scriptList.Type, scriptList.Key);
527 EnsureEndScriptBlock (writer);
528 writer.WriteLine (scriptList.Script);
531 scriptList = scriptList.Next;
533 EnsureEndScriptBlock (writer);
536 bool _scriptTagOpened;
538 void EnsureBeginScriptBlock (HtmlTextWriter writer) {
539 if (!_scriptTagOpened) {
540 WriteBeginScriptBlock (writer);
541 _scriptTagOpened = true;
545 void EnsureEndScriptBlock (HtmlTextWriter writer) {
546 if (_scriptTagOpened) {
547 WriteEndScriptBlock (writer);
548 _scriptTagOpened = false;
552 internal void RestoreEventValidationState (string fieldValue)
554 if (!page.EnableEventValidation || fieldValue == null || fieldValue.Length == 0)
556 IStateFormatter fmt = page.GetFormatter ();
557 eventValidationValues = (int []) fmt.Deserialize (fieldValue);
558 eventValidationPos = eventValidationValues.Length;
561 internal void SaveEventValidationState ()
563 if (!page.EnableEventValidation)
566 string eventValidation = GetEventValidationStateFormatted ();
567 if (eventValidation == null)
570 RegisterHiddenField (EventStateFieldName, eventValidation);
573 internal string GetEventValidationStateFormatted ()
575 if (eventValidationValues == null || eventValidationValues.Length == 0)
578 if(page.IsCallback && !_hasRegisteredForEventValidationOnCallback)
581 IStateFormatter fmt = page.GetFormatter ();
582 int [] array = new int [eventValidationPos];
583 Array.Copy (eventValidationValues, array, eventValidationPos);
584 return fmt.Serialize (array);
587 internal void WriteExpandoAttributes (HtmlTextWriter writer)
589 if (expandoAttributes == null)
593 WriteBeginScriptBlock (writer);
595 foreach (string controlId in expandoAttributes.Keys) {
596 writer.WriteLine ("var {0} = document.all ? document.all [\"{0}\"] : document.getElementById (\"{0}\");", controlId);
597 ListDictionary attrs = (ListDictionary) expandoAttributes [controlId];
598 foreach (string attributeName in attrs.Keys) {
599 writer.WriteLine ("{0}.{1} = \"{2}\";", controlId, attributeName, attrs [attributeName]);
602 WriteEndScriptBlock (writer);
606 internal const string SCRIPT_BLOCK_START = "//<![CDATA[";
607 internal const string SCRIPT_BLOCK_END = "//]]>";
608 internal const string SCRIPT_ELEMENT_START = @"<script type=""text/javascript"">" + SCRIPT_BLOCK_START;
609 internal const string SCRIPT_ELEMENT_END = SCRIPT_BLOCK_END + "</script>";
611 internal static void WriteBeginScriptBlock (HtmlTextWriter writer)
613 writer.WriteLine (SCRIPT_ELEMENT_START);
616 internal static void WriteEndScriptBlock (HtmlTextWriter writer)
618 writer.WriteLine (SCRIPT_ELEMENT_END);
621 internal void WriteHiddenFields (HtmlTextWriter writer)
623 if (hiddenFields == null)
627 writer.RenderBeginTag (HtmlTextWriterTag.Div);
628 int oldIndent = writer.Indent;
632 foreach (string key in hiddenFields.Keys) {
633 string value = hiddenFields [key] as string;
638 writer.Write ("<input type=\"hidden\" name=\"{0}\" id=\"{0}\" value=\"{1}\" />", key, HttpUtility.HtmlAttributeEncode (value));
640 writer.Indent = oldIndent;
641 writer.RenderEndTag (); // DIV
646 internal void WriteClientScriptInclude (HtmlTextWriter writer, string path, Type type, string key) {
647 if (!page.IsMultiForm)
648 writer.WriteLine ("<script src=\"{0}\" type=\"text/javascript\"></script>", path);
650 string scriptKey = "inc_" + (type.FullName + key).GetHashCode ().ToString ("X");
651 writer.WriteLine ("<script type=\"text/javascript\">");
652 writer.WriteLine (SCRIPT_BLOCK_START);
653 writer.WriteLine ("if (!window.{0}) {{", scriptKey);
654 writer.WriteLine ("\twindow.{0} = true", scriptKey);
655 writer.WriteLine ("\tdocument.write('<script src=\"{0}\" type=\"text/javascript\"><\\/script>'); }}", path);
656 writer.WriteLine (SCRIPT_BLOCK_END);
657 writer.WriteLine ("</script>");
661 internal void WriteClientScriptBlocks (HtmlTextWriter writer)
663 WriteScripts (writer, clientScriptBlocks);
666 internal void WriteStartupScriptBlocks (HtmlTextWriter writer)
668 WriteScripts (writer, startupScriptBlocks);
671 internal void WriteArrayDeclares (HtmlTextWriter writer)
673 if (registeredArrayDeclares != null) {
675 WriteBeginScriptBlock (writer);
676 IDictionaryEnumerator arrayEnum = registeredArrayDeclares.GetEnumerator();
677 while (arrayEnum.MoveNext()) {
678 if (page.IsMultiForm)
679 writer.Write ("\t" + page.theForm + ".");
681 writer.Write ("\tvar ");
682 writer.Write(arrayEnum.Key);
683 writer.Write(" = new Array(");
684 IEnumerator arrayListEnum = ((ArrayList) arrayEnum.Value).GetEnumerator();
686 while (arrayListEnum.MoveNext()) {
691 writer.Write(arrayListEnum.Current);
693 writer.WriteLine(");");
695 WriteEndScriptBlock (writer);
700 internal string GetClientValidationEvent (string validationGroup) {
701 if (page.IsMultiForm)
702 return "if (typeof(" + page.theForm + ".Page_ClientValidate) == 'function') " + page.theForm + ".Page_ClientValidate('" + validationGroup + "');";
703 return "if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate('" + validationGroup + "');";
706 internal string GetClientValidationEvent ()
708 if (page.IsMultiForm)
709 return "if (typeof(" + page.theForm + ".Page_ClientValidate) == 'function') " + page.theForm + ".Page_ClientValidate();";
710 return "if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate();";
714 internal string WriteSubmitStatements ()
716 if (submitStatements == null) return null;
718 StringBuilder sb = new StringBuilder ();
719 ScriptEntry entry = submitStatements;
720 while (entry != null) {
721 sb.Append (EnsureEndsWithSemicolon (entry.Script));
724 RegisterClientScriptBlock (GetType(), "HtmlForm-OnSubmitStatemen",
726 " + page.WebFormScriptReference + @".WebForm_OnSubmit = function () {
727 " + sb.ToString () + @"
731 return "javascript:return " + page.WebFormScriptReference + ".WebForm_OnSubmit();";
734 internal static string GetScriptLiteral (object ob)
738 else if (ob is string) {
739 string s = (string)ob;
743 for (int i = 0; i < len; i++)
744 if (s [i] == '\\' || s [i] == '\"') {
750 return string.Concat ("\"", s, "\"");
752 StringBuilder sb = new StringBuilder (len + 10);
755 for (int si = 0; si < len; si++) {
758 else if (s [si] == '\\')
765 return sb.ToString ();
766 } else if (ob is bool) {
767 return ob.ToString ().ToLower (Helpers.InvariantCulture);
769 return ob.ToString ();
773 sealed class ScriptEntry
775 public readonly Type Type;
776 public readonly string Key;
777 public readonly string Script;
778 public readonly ScriptEntryFormat Format;
779 public ScriptEntry Next;
781 public ScriptEntry (Type type, string key, string script, ScriptEntryFormat format) {
789 enum ScriptEntryFormat
797 internal static string EnsureEndsWithSemicolon (string value) {
798 if (value != null && value.Length > 0 && value [value.Length - 1] != ';')