maxJsonLength property

I was working on a asp:DataGrid table , where in every cell there is an asp:Textbox for immediate editing (think to a sort of Excel).
Then with an html button is called the javascript method save:

function save() {
    $get('ContentPlaceHolder1_UpdateProgress1').style.display = 'block';
    var strSql = "";
    var strPartSql = "";
    $("input.editqt").each(function () {
        var strHtmlId = this.id;
        var arrValues = strHtmlId.split("_");
        var strId = arrValues[1];
        var strValue = $("#" + strHtmlId).val();
        if (strValue.trim() === "")
            strValue = "0";
        strPartSql = "UPDATE TSG123_VoyageRes SET Qt = " + strValue + " WHERE ID_Voyage_Res = " + strId + "; ";
        strSql += strPartSql;
    });
    PageMethods.SaveMatrix(strSql, saveCallback, saveErrorCallback, "save");
}

function saveCallback(result, response, context) {
    $get('ContentPlaceHolder1_UpdateProgress1').style.display = 'none';
    window.alert("Save success.");
}

function saveErrorCallback(result, response, context) {
    $get('ContentPlaceHolder1_UpdateProgress1').style.display = 'none';
    if (result._timedOut)
        window.alert("Timeout saving");
    else {
        if (result !== null)
            window.alert(result.get_message());
        else
            window.alert("Error saving.");
    }
}

The javascript code calls an PageMethod (the web site uses ASP.NET 4, VB.NET , Microsoft Ajax Toolkit), defined as:

<System.Web.Script.Services.ScriptMethod(), System.Web.Services.WebMethod()> _
Public Shared Sub SaveMatrix(ByVal sql As String)
    UserFrameworkClass.AccessDB.Database.SetData(sql, Data.CommandType.Text)
End Sub

In practice from the javascript side is generated (not the best method..) a long SQL string, in the Page method is launched “as is” into SQL Server.
At a certain point the datagrid became bigger, and trying to save , crash: error from javascript:
“Error during serialization or deserialization using the JSON JavaScriptSerializer. The length of the string exceeds the value set on the maxJsonLength property.”
The solution was to add these lines in web.config:

<system.web.extensions>
  <scripting>
    <webServices>
      <jsonSerialization maxJsonLength="2147483647"/>
    </webServices>
  </scripting>
</system.web.extensions>

No more errors, until these pages will be rewritten in a more solid manner.

Arbitrary values in asp:Datagrid

Working at a ships loading planner, i received this request: we can have more work turns, for every turn one or more squads (from 1 to 5).

But there should be an optional special squad , the “0” squad, that must be represented with the “SA” string; note also that only on the first row for every turn must be visible an Word export icon.

The work turn on the db is an Int.. how to write “SA”?

We are speaking of an .NET 4 solution, using VB.NET, the DataGrid is defined as

<asp:DataGrid ID="DG" runat="server" AutoGenerateColumns="False" ShowHeader="true"
    EnableViewState="true" CssClass="DGBorder" Width="99%" Height="1%" HorizontalAlign="left">
    <AlternatingItemStyle CssClass="DGRow2" />
    <SelectedItemStyle CssClass="DGSelect" />
    <ItemStyle CssClass="DGRow1" />
    <HeaderStyle />
    <Columns>
        <asp:BoundColumn DataField="Day" HeaderText="Date" DataFormatString="{0:dd/MM/yyyy}">
            <ItemStyle CssClass="DGBorder" Width="200px" HorizontalAlign="Left" />
            <HeaderStyle HorizontalAlign="Center" Width="200px" />
        </asp:BoundColumn>
        <asp:BoundColumn DataField="Turn" HeaderText="Turn">
            <ItemStyle CssClass="DGBorder" Width="100px" HorizontalAlign="Left" />
            <HeaderStyle HorizontalAlign="Center" Width="100px" />
        </asp:BoundColumn>
        <asp:BoundColumn DataField="Hours" HeaderText="Hours">
            <ItemStyle CssClass="DGBorder" Width="3em" HorizontalAlign="Center" />
            <HeaderStyle HorizontalAlign="Center" Width="3em" />
        </asp:BoundColumn>
        <asp:BoundColumn DataField="Squad" HeaderText="Squad">
            <ItemStyle CssClass="DGBorder" Width="100px" HorizontalAlign="Center" />
            <HeaderStyle HorizontalAlign="Center" Width="100px" />
        </asp:BoundColumn>
        <asp:ButtonColumn DataTextField="Squad" HeaderText="Fold" DataTextFormatString="<img src=Imgs\word.png border=0>" CommandName="PrintFold">
            <ItemStyle CssClass="DGBorder" Width="100px" HorizontalAlign="Center" />
            <HeaderStyle HorizontalAlign="Center" Width="100px" />
        </asp:ButtonColumn>
    </Columns>
</asp:DataGrid>

The solution is the SetRenderMethodDelegate method:

Protected Sub DG_ItemCreated(sender As Object, e As System.Web.UI.WebControls.DataGridItemEventArgs) Handles DG.ItemCreated
    If e.Item.ItemType = ListItemType.Item Or e.Item.ItemType = ListItemType.AlternatingItem Or e.Item.ItemType = ListItemType.SelectedItem Then
        Dim drv As DataRowView = TryCast(e.Item.DataItem, DataRowView)
        If drv Is Nothing Then
            Exit Sub
        End If
        If strDataRif = String.Empty Then
            strDataRif = drv.Row("Day").ToString()
            strTurnoRif = drv.Row("Turn").ToString()
        End If
        strTempData = drv.Row("Day").ToString()
        strTempTurno = drv.Row("Turn").ToString()
        If strTempData <> strDataRif Or strTempTurno <> strTurnoRif Or boolForceFirst Then
            strDataRif = strTempData
            strTurnoRif = strTempTurno
            boolForceFirst = False
            e.Item.Controls(3).SetRenderMethodDelegate(New RenderMethod(AddressOf RenderCustom))
        Else
            e.Item.Controls(4).Controls(0).Visible = False
        End If
    End If
End Sub

Protected Sub RenderCustom(ByVal writer As System.Web.UI.HtmlTextWriter, ByVal Container As System.Web.UI.Control)
    If DirectCast(Container, System.Web.UI.WebControls.TableCell).Text = "1" Then
        writer.Write("1")
    Else
        writer.Write("SA")
    End If
End Sub

If we asked to create the SA squad, we have 0 as number and then must be changed with “SA” , otherwise the squad numbering begins with 1 and must not be changed.
The result:

grid

Strange behaviors in Crystal

I found today a strange behavior with Crystal Reports .NET , using Visual Studio 2010.
In a recordsource there are 2 numeric (integer) columns that were displayed in the preview window, inside Visual Studio.
At runtime for these 2 colums, nothing: no values displayed, even the headers.
This in a subreport; this one in origin had the same SQL of the main report.
Then the datasource of the subreport was changed, some columns previously used in the report was deleted, substituted with other columns, with different names.
I tried even to change the original subreport SQL (a JOIN between 2 CTE, inside every CTE a PIVOT) with a plain SQL (SELECT ‘value’ AS columnname) but without success: still no data displayed at runtime inside the subreport.
The solution is very strange, a Crystal formula (Vb syntax):

If IsNull({Command.TotalTSG}) then
    formula = 0
else
    formula = {Command.TotalTSG}
end if

If the fields are placed directly in the Crystal editor, data visible in preview but nothing at runtime; instead it works also at runtime with Crystal formulas: data displayed.
Note: all of the remaining fields (also integers) are displayed if placed directly on the report (but are fields in common as name between the main and the subreport), even without a formula.
Mysteries of Crystal Reports.

Outlining in Eclipse

In Visual Studio 2013 by right clicking the code there is an useful menu for code outlining


Is so useful CTRL+MO…

In Eclipse Luna i struggled for a while in order to find the same feature, not easy to discover;

CTRL+SHIFT+/ on numeric keypad is the same of CTRL+MO on Vs2013; CTRL+SHIFT+* (on numeric keypad) is the same of CTRL+ML

The trick works only if is activated the folding: by right clicking on the line numbers appears the menu, with the folding activated are visible the controls (in yellow in the image) for expanding and retracting the code.

eclipsefolding

Categories: .NET, Android

WebAPI2 errors management in Android

In my last project is used an SQL Server 2014 Phones table, with an Android app that register the device itself in this table via an WebAPI2 call.

The table is simple:


We have an primarykey named “PK_Phones” on PhoneId.

The PhoneID is the IMEI code, if the device has phone capabilities and is installed an working SIM, or the Android ID.

There are other common fields for multiuser management as the timestamp which is very useful with the Entity Framework 6 used in this solution.

The Web counterpart (created with Visual Studio 2013) is a common MVC5 project using WebAPI2 with Breeze, Angular , Bootstrap, Modernizr, Moment, Ninject, Toastr…an huge blob installed from NuGet using the HotTowel package (Ninject , Jasmine for testing and other package needs to be installed by hand).

The first move was to create an ADO.NET Entity Data Model (here i don’t speak in detail of the project organization) and using “Code first from Database” i created the class representing my table:

namespace net.studioalessi.walkad.dl
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Data.Entity.Spatial;

    public partial class Phones
    {
        [Key]
        [StringLength(20)]
        public string PhoneId { get; set; }

        [StringLength(30)]
        public string Manufacturer { get; set; }

        [StringLength(20)]
        public string Model { get; set; }

        [StringLength(150)]
        public string Notes { get; set; }

        [StringLength(20)]
        public string PhoneNumber { get; set; }

        public bool Banned { get; set; }

        [StringLength(50)]
        public string UtenteModifica { get; set; }

        public DateTime? OraModifica { get; set; }

        [StringLength(50)]
        public string UtenteInserimento { get; set; }

        public DateTime? OraInserimento { get; set; }

        [Column(TypeName = "timestamp")]
        [MaxLength(8)]
        [Timestamp]
        public byte[] SSMATimeStamp { get; set; }
    }
}

Then the necessary Controller (right click on Controllers folder->add new scaffolded item->


Web API 2 Controller with actions, using Entity Framework as in figure.

Using the previous Phones class we generate the Controller and the Context, in our case the context is the class PhonesContext:

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web;

namespace net.studioalessi.walkad.dl
{
    public class PhonesContext : DbContext
    {
        public PhonesContext()
            : base("name=WalkadConn")
        {
        }

        // no virtual ? in the previous EF version was generated as virtual...
        public DbSet<Phones> Phones { get; set; }

        // Also the code below was generated automatically in the previous version.
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Phones>()
                .Property(e => e.UtenteModifica)
                .IsUnicode(false);

            modelBuilder.Entity<Phones>()
                .Property(e => e.UtenteInserimento)
                .IsUnicode(false);

            modelBuilder.Entity<Phones>()
                .Property(e => e.SSMATimeStamp)
                .IsFixedLength();
        }
    }
}

And the Controller is changed from the generated version to an BreezeController

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Description;
using Walkad.Models;
using net.studioalessi.walkad.dl;
using Breeze.WebApi2;

namespace Walkad.Controllers
{
    [BreezeController]
    public class PhonesController : ApiController
    {
        private IPhoneRepository _repo;

        public PhonesController(IPhoneRepository repo)
        {
            _repo = repo;
        }

        [HttpGet]
        public string Metadata()
        {
            return _repo.MetaData;
        }

        [HttpPost]
        [AllowAnonymous]
        public async Task<IHttpActionResult> RegisterImei(PhoneModel model)
        {
            PhonesContext objT = new PhonesContext();
            try
            {
                Phones objTrace = new Phones
                {
                    UtenteInserimento = "RemoteUser",
                    OraInserimento = DateTime.Now,
                    Model = model.Model,
                    Manufacturer = model.Manufacturer,
                    PhoneId = model.Imei
                };
                //
                objT.Phones.Add(objTrace);
                await objT.SaveChangesAsync();
                return Ok();
            }
            catch (Exception ex)
            {
                return BadRequest(ex.ToString());
            }
            finally
            {
                //
            }
        }
    }
}

The PhoneModel was created by hand and is the model representing the JSON string sent from the client (Web site or Android app, as in this case)

public class PhoneModel
{
    [Required]
    [StringLength(15)]
    public string Imei { get; set; }

    [StringLength(30)]
    public string Manufacturer { get; set; }

    [StringLength(20)]
    public string Model { get; set; }
}

At this point we can try the service from Fiddler


By clicking “Execute” we can see that our call was successful


And in SQL Management Studio we can see the new record:


Ok , we can create the Android app with Eclipse Luna (with the Android Developer Tools).

I don’t show the details, i created an simple Android app with an menu item that calls the routine named registerThisDevice:

private void registerThisDevice() {
    SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
    String strBasePath = sharedPrefs.getString((String)getText(R.string.pref_httpbase), "http://192.168.0.77/Walkad");
    new WebApiRegisterImei().execute(strBasePath + "/api/Phones/RegisterImei");
}

Note the i’m using the IP , the tablet used for the test is in the same WiFi network but is not Windows, is not able to resolve the NETBIOS name.

Note also that we web address is registered in a Preference that defaults to my WiFi address.

In order to follow the best practices , the routine WebApiRegisterImei is an AsyncTask so is called with .execute() passing the server address as String argument (in every case , if we don’t use an AsyncTask we get an NetworkOnMainThread exception):

private class WebApiRegisterImei extends AsyncTask<String, Void, JSONObject> {
    ProgressDialog myProgressBar;
    public String ExportResult = "";
    public String LastException = "";
    public Exception LastExceptionObject;       

    protected JSONObject doInBackground(String... params) {
        BufferedInputStream  in = null;
        HttpURLConnection urlConnection = null;
        try {
            URL url = new URL(params[0]);
            urlConnection = (HttpURLConnection) url.openConnection();
            // the Android docs says that HttpURLConnection uses the GET method by default. It will use
            // POST if setDoOutput(true) has been called. Other HTTP methods
            // (OPTIONS, HEAD, PUT, DELETE and TRACE) can be used with
            // setRequestMethod(String). Anyway forcing POST as request method is useful.
            urlConnection.setRequestMethod("POST");
            urlConnection.setDoOutput(true);
            urlConnection.setDoInput(true);
            //
            JSONStringer vm;
            vm = new JSONStringer()
                    .object().key("Imei").value(MainActivity.ImeiId)
                             .key("Manufacturer").value(android.os.Build.MANUFACTURER)
                             .key("Model").value(android.os.Build.MODEL)
                    .endObject();

            urlConnection.setFixedLengthStreamingMode(vm.toString().getBytes().length);
            urlConnection.setRequestProperty("Content-Type", "application/json");
            urlConnection.setRequestProperty("charset", "utf-8");
            DataOutputStream wr = new DataOutputStream (
                    urlConnection.getOutputStream ());
                    wr.writeBytes (vm.toString());
                    wr.flush ();
                    wr.close ();                           
            //
            int statusCode = urlConnection.getResponseCode();
            if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
                // handle unauthorized (if service requires user login)
            } else if (statusCode != HttpURLConnection.HTTP_OK) {
                InputStream errorstream = urlConnection.getErrorStream();
                BufferedReader br = null;
                if (errorstream == null){
                    InputStream inputstream = urlConnection.getInputStream();
                    br = new BufferedReader(new InputStreamReader(inputstream));
                }else{
                    br = new BufferedReader(new InputStreamReader(errorstream));
                }
                String strResponse = "";
                String strTemp;
                while ((strTemp = br.readLine()) != null){
                    strResponse += strTemp;
                }   
                strResponse = strResponse.toLowerCase();
                if(strResponse.contains("pk_phones"))
                {
                    ExportResult = getErrorDuplicated();
                    return null;
                }
            }
            //
            ExportResult = getExportCompleted();
        } catch (Exception e) {
            Mint.logException(e);
            LastException = e.toString();
            LastExceptionObject = e;                
        } finally {
            if (in != null){
                try {
                    in.close();
                } catch (IOException e) {
                    Mint.logException(e);
                }
            }
            if(urlConnection != null){
                urlConnection.disconnect();  
            }
        }
        return null;
    }

    private String getErrorDuplicated() {
        return (String)getText(R.string.imeiduplicatederr);
    }

    private String getExportCompleted(){
        return (String)getText(R.string.registerimeidone);
    }           

    @Override
    protected void onPreExecute() {
        try {
            myProgressBar = new ProgressDialog(MainActivity.thisMainActivity, ProgressDialog.STYLE_SPINNER);    
            myProgressBar.setMessage((String)getText(R.string.registerimeiwait));
            myProgressBar.show();
        } catch (Exception e) {
            Mint.logException(e);
            Toast.makeText(getBaseContext(), (String)getText(R.string.genericerror),Toast.LENGTH_LONG).show();                  
        }
    }

    @Override
    protected void onPostExecute(JSONObject result) {
        try {
            myProgressBar.dismiss();
            if(LastException.length() > 0){
                Toast.makeText(getBaseContext(), (String)getText(R.string.genericerror),Toast.LENGTH_LONG).show();   
                Mint.logException(LastExceptionObject);
            }else{
                Toast.makeText(MainActivity.thisMainActivity, ExportResult, Toast.LENGTH_LONG).show();
            }
        } catch (Exception e) {
            Mint.logException(e);
            Toast.makeText(getBaseContext(), (String)getText(R.string.genericerror),Toast.LENGTH_LONG).show();                  
        }
    }
}

Examining the code , we can note that onPreExecute and onPostExecute are the only places where we can interact with the main thread and relative controls (the progress bar) so in the background part we register the exceptions details in one string and one object, examining them on the onPostExecute.

Note the use of the JSONStringer: debugging the code you can see that is created the JSON string as used in Fiddler.

If all is ok, we can see that in SQL Server is written a new row in the Phones table.

The test for statusCode != HttpURLConnection.HTTP_OK was the difficult part to discover how it works, the subject of this post.

If you reissue the command from the Android app on the same device you violate the primary key, so the WebAPI code goes in the Exception and is sent to the client “BadRequest” (HTTP error 400) instead of “Ok”(200), sending as other info ex.ToString().

The management of the returned error is done as you can read in the code: we need to examine the returned stream , by reading this stream we can create a string that contains the original ex.ToString, for example

{"$id":"1","$type":"System.Web.Http.HttpError, System.Web.Http","Message":"System.Data.Entity.Infrastructure.DbUpdateException: 
An error occurred while updating the entries. See the inner exception for details. ---> 
System.Data.Entity.Core.UpdateException: An error occurred while updating the entries. 
See the inner exception for details. ---> System.Data.SqlClient.SqlException: Violation of PRIMARY KEY constraint 'PK_Phones'. 
Cannot insert duplicate key in object 'dbo.Phones'. The duplicate key value is…..

So in the Android code we search if the string contains the name of the primary key , giving to the user an scoped message, or we give to the user an generic message and the code uses Splunk Mint Express for registering all infos about the unmanaged exception (that could be an network failure, and so on).

Activity not created in Eclipse

Every time that there is an Android update, i’m prepared to troubles.

Recently i updated ADT to version 24.0.2 , and created a new project.

When i tried to add a new Activity, nothing! no xml for the screen and no Class.

In Logcat only an obscure message “Style contains key with bad entry”.

Also troubles with the appcompat_7 library, importing the new version in the workspace an message “No resource found that matches the given name: attr ‘android:actionModeShareDrawable'”

For appcompat, the solution was to change in manifest.xml the targetSDK to “android-21″, after a Project->Clean problem resolved.

After some attempts, the solution for the Activity not created was to uninstall the ADT (Help->installation details->select all ADT->uninstall)


And then reinstall from Help->Install new software:


Finally, it took some additional time to Eclipse for loading but it seems that adding -clean in the Windows shortcut (“<yourpath>\eclipse.exe -clean”) is useful to prevent minor troubles by deleting old cache entries.

Categories: Android

PlaceMinder 2

Finally i published on GooglePlay my last work in Android.

It was an interesting exercise, but it is also the base for a new commercial project of Android apps connected to an ASP.NET WebApi2 site, using ASP.NET Identity2.

There are many technologies used: connection to an SQLite database, use of the GPS, sounds and vibrations, Google maps, background services, AsyncTask and so on.

For error logging is used Splunk MINT Express, that gives by email very detailed informations on crashes and makes very easy to understand where is the bug.

Categories: Android

The zipalign tool was not found in the sdk

I’m using Eclipse Luna , the new version, for some updates to my Android projects (some news about in the next post).

Today i was the first time that i attempt to publish a signed version of a .apk, and since is all a brand new installation (Windows 8.1, Eclipse Luna) ..troubles.

Attempting to publish the release APK from Eclipse i got the error used as title of this post.

Fortunately this time the solution was simple: it is requested to install (Window->Android SDK Manager) the Rev. 20 of Android SDK Build Tools


Categories: Android

Autocomplete extender with Id and Description

It was a very long time ago that i worked with the Ajax Toolkit, but sometimes you are requested to implement old projects..

And the language is VB.NET, that i was not using since 2003.

The request was to implement an Autocomplete extender from a customers list (from a Sql Server table), but in the database is requested to write the autoincrement ID, not the description.

In order to implement the ajax toolkit, in the .aspx markup must be added the header:

<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="cc1" %>

With the obvious adding of a reference , for the ASP.NET project, to AjaxControlToolkit.dll

A simple autoextender can be written as

<asp:TextBox ID="txtAgency" runat="server" ></asp:TextBox>
<cc1:AutoCompleteExtender ID="aceAgency" FirstRowSelected="false" MinimumPrefixLength="1" TargetControlID="txtAgency" ServiceMethod="GetListAgencies" runat="server">
</cc1:AutoCompleteExtender>

in the aspx page; in the code behind for this page we can implement a page method (the “ServiceMethod”) as

<System.Web.Script.Services.ScriptMethod(), _
System.Web.Services.WebMethod()> _
Public Shared Function GetListAgencies(ByVal prefixText As String, ByVal count As Integer) As List(Of String)
    Dim conn As SqlConnection = New SqlConnection
    '
    conn.ConnectionString = <string from web.config>
    Dim cmd As SqlCommand = New SqlCommand
    cmd.CommandText = "SELECT AnagId, AnagName FROM [dbo].[SomeTableOrView] WHERE AnagName LIKE @SearchText + '%'"
    cmd.Parameters.AddWithValue("@SearchText", prefixText)
    cmd.Connection = conn
    conn.Open()
    Dim lstAgencies As List(Of String) = New List(Of String)
    Dim sdr As SqlDataReader = cmd.ExecuteReader
    While sdr.Read
        lstAgencies.Add(sdr("AnagName").ToString))
    End While
    conn.Close()
    Return lstAgencies
End Function

This works, we type one letter (MinimumPrefixLength=”1″) on the keyboard and we are proposed with the list of names beginning with the typed letter.

But from the database in our sample we have only the AnagName value , the AnagId is not connected in some manner with the selected item: if we need to save that Id instead of the name this approach is not valid.

In order to save that id we need some javascript code and another approach in the web method.

First, this is the new aspx markup:

<asp:TextBox ID="txtAgency" runat="server"></asp:TextBox>
<cc1:AutoCompleteExtender ID="aceAgency" FirstRowSelected="false" TargetControlID="txtAgency" ServiceMethod="GetListAgencies" OnClientHidden="onClientHiddenAgency" MinimumPrefixLength="1"  EnableCaching="true" OnClientItemSelected="onClientItemSelectedAgency" OnClientPopulating="onClientPopulatingAgency" runat="server">
</cc1:AutoCompleteExtender>
<input type="hidden" id="hdnAgencyId" name="hdnAgencyId" runat="server" value="0" />

The OnClient… properties are the names of javascript functions; the EnableCaching is true because the underlying data are in this case very static.

Note that there is an hidden field where to save the requested Id and this is runat=server because we need to refer this field when we will save the record.

The javascript functions (ok, not the best javascript code…):

var boolChosenAgency = false;

function onClientItemSelectedAgency(source, e) {
    window.document.getElementById("<%=hdnAgencyId.ClientID%>").value = e.get_value();
    boolChosenAgency = true;
}

function onClientHiddenAgency(source, e) {
    if (!boolChosenAgency) {
        window.document.getElementById("<%=hdnAgencyId.ClientID%>").value = "0";
        window.document.getElementById("<%=txtAgency.ClientID%>").value = "";
    }
}

function onClientPopulatingAgency(source, e) {
    window.document.getElementById("<%=hdnAgencyId.ClientID%>").value = "0";
    boolChosenAgency = false;
}

What happens with this code is:

  • When the user begins to write, the Id value is changed to 0 and boolChosenAgency is set to false (“the user has not stille chosen an valid value”)
  • Only when an item is chosen the Id (coming from e.get_value()) is written in the hidden field
  • OnClientHiddenAgency is always executed, if the user has opened the list but has not clicked on a value then the id value is changed to 0

On the code behind so we must verify : if the hidden id is 0 and/or the textbox is empty the the id must be null on the db, otherwise we can write the int value.

The scope of the code is also: if the user write a not valid text, the Id is invalidated.

But how we can have the id in e.get_value() ?

This is done with this code for the web method:

<System.Web.Script.Services.ScriptMethod(), _
System.Web.Services.WebMethod()> _
Public Shared Function GetListAgencies(ByVal prefixText As String, ByVal count As Integer) As List(Of String)
    Dim conn As SqlConnection = New SqlConnection
    '
    conn.ConnectionString = <string from web.config>
    Dim cmd As SqlCommand = New SqlCommand
    cmd.CommandText = "SELECT AnagId, AnagName FROM [dbo].[SomeTableOrView] WHERE AnagName LIKE @SearchText + '%'"
    cmd.Parameters.AddWithValue("@SearchText", prefixText)
    cmd.Connection = conn
    conn.Open()
    Dim lstAgencies As List(Of String) = New List(Of String)
    Dim sdr As SqlDataReader = cmd.ExecuteReader
    While sdr.Read
        lstAgencies.Add(AjaxControlToolkit.AutoCompleteExtender.CreateAutoCompleteItem(sdr("AnagName").ToString, sdr("AnagID").ToString))
    End While
    conn.Close()
    Return lstAgencies
End Function

The trick is done using the CreateAutoCompleteItem method as in the above code.

In the javascript code we can use e.get_value for the ID and e.get_text() for the name (“AnagName” in the sample).

Follow

Get every new post delivered to your Inbox.