|
|
Front Page News
-
If you’re a site admin or asp.net developer for an internet site, you certainly need to look into sitemaps, if you want to perform SEO. It’s not necessary to simply crawl your own site and then to give every page a priority, but consider this for a forum or other pages which are irregularly or often updated. If you don’t want to have crawlers do unneeded roundtrips, implement a sitemap. ‘robots.txt’ should contain a reference to your map eg. Sitemap: http://www.myfantasticsite.com/sitemap.xml Ideas of this class, written using C#, can be found anywhere on the net. However, as some might know me, I like it to be finished and neat and a self-supporting class ready for usage (e.g. it must not be written to a string to add or remove wished attributes that the serializer could not handle). The following things are solved. Since ‘changefreq’ and ‘lastmod’ and ‘priority’ are optional values, you don’t want the XmlSerializer to create empty tags! This is done by adding a DefaultValue attribute. It will cause XmlSerializer to check the current value against the default value. If they are equal, it is considered to be an empty non existing tag. Remember, that the defaultvalues need to be out of the range of possible values! Therefore, EnumChangeFreq contains an extra member ‘notused’ An other solved issue is the xsi:schemaLocation attribute at documentelement level. You cannot add this using the XmlSerializerNamespaces class. It would simply fail on xsi:schemaLocation. Do not use: (XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); ns.Add("xsi:schemaLocation", "http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd");)
The solution was to serialize the XML to a XmlDocument and to append the needed attributes as seen below. You can use the class as follows. UrlSet retVal = new UrlSet(); (simplified you can add your algorithm to retrieve the real lastmodified time) retVal.AddUrl(new Url() { LastModifiedDateTime = DateTime.Now.ToUniversalTime(), Loc = “http://www.myfantasticsite.com/blah.aspx”) }); Retrieve the XML sitemap string. string xml = null; using (var io = (MemoryStream)retVal.ToStream()) { xml = new UTF8Encoding().GetString(io.ToArray()); } Since method returns a Stream object, you also can use it to directly serialize it to disk. There might be no need for roundtrips to slower string(s). using System; using System.Xml; using System.Xml.Serialization; using System.ComponentModel; using System.IO; namespace adccure { public enum EnumChangeFreq { notset, always, hourly, daily, weekly, monthly, yearly, never } [XmlRoot( ElementName = "urlset", Namespace = "http://www.sitemaps.org/schemas/sitemap/0.9")] public sealed class UrlSet { private Url[] _url; public void AddUrl(Url url) { int l = _url.Length + 1; Array.Resize(ref _url, l); _url[l - 1] = url; } public UrlSet() { _url = new Url[0]; } [XmlElement(ElementName = "url")] public Url[] url { get { return _url; } set { _url = value; } //bogus } /// <summary> /// serializes the UrlSet to a sitemap.xsd conform string ready for saving to disk. /// </summary> /// <returns>a Stream object</returns> public Stream ToStream() { XmlSerializer xmlser = new XmlSerializer(this.GetType()); XmlDocument doc = new XmlDocument(); var io = new MemoryStream(); xmlser.Serialize(io, this); io.Position = 0; doc.Load(io); var attr = doc.CreateAttribute("schemaLocation", "http://www.w3.org/2001/XMLSchema-instance"); attr.Value = "http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"; doc.DocumentElement.Attributes.Append(attr); io.Position = 0; doc.Save(io); io.Position = 0; return io; } } public sealed class Url { private string _loc; private DateTime _lastmod; private float _priority; private EnumChangeFreq _changefreq; public Url() { //setting defaults _changefreq = EnumChangeFreq.notset; _priority = 0.0F; _lastmod = DateTime.MinValue; } [XmlElement(ElementName = "loc")] public string Loc { get { return _loc; } set { if (string.IsNullOrEmpty(value)) { throw new ArgumentNullException(); } if (value.Length < 12 || value.Length > 2048) { throw new ArgumentException("loc must be between 12 and 2048 in length"); } _loc = value; } } [XmlElement(ElementName = "lastmod"), DefaultValue(typeof(DateTime), "1-1-0001")] public DateTime LastModifiedDateTime { get { return _lastmod; } set { _lastmod = new DateTime(value.Year, value.Month, value.Day, value.Hour, value.Minute, value.Second, value.Kind); } } [XmlElement(ElementName = "changefreq")] [DefaultValue(EnumChangeFreq.notset)] public EnumChangeFreq ChangeFreq { get { return _changefreq; } set { _changefreq = value; } } [XmlElement(ElementName = "priority")] [DefaultValue(0.0F)] public float Priority { get { return _priority; } set { if (value < 0 || value > 1.0) { throw new ArgumentException("Must be between 0 and 1"); } _priority = value; } } } } Tags van Technorati: C#, sitemaps, XmlSerializer
|
-
Figure 1: Our custom pager in action! I never have liked the concept of storing all the data in whatever form (DataTable/Lists of records/etc.) to the ASP.NET gridview control and having it automatically manage paging for me. This could however be improved using Visual Studio 2008 wizards. However, this requires writing stored procedures. I’ve got a concept for you, which works without a lot of extra work. The concept is: - Inherit from asp:GridView and override the PageCount and the PageIndex properties
- Create in instance of my CustomPager class at DataBinding time.
The result is as shown in figure 1: It adheres to PageButtonCount, to any styles that you have applied to the GridView and it features a ‘jump to’ page input box. The ‘native’ event handling in your ASPX, still can be maintained by this code since it emulates the PageIndexChanging event. Other solutions, implement an AJAX updatepanel per row. This really minimizes unnecessary refreshing of grid data. However, I don’t mind if say, 30 rows are being pulled from a DB and bound to a GridView. If we do so, we also get the best of two worlds, in one world, we get all data and have a fresh update of the real table rows, and in the other world, we have just the active row being fetched and we could end up having outdated data on our screen because of (for instance) co-writers/typers who updated rows at the database which are not reflected at our screen in grid (which is displayed using a gridview). So, in other words, I like this pager control since it is a balanced solution! It has been tested on 10,000 records. WHen I would page to for instance, page 900, it really is a matter of fractions of a second to get a response. (For paging solutions on SQL server data tables which contain say millions of rows, we would need a more sophisticated approach). Here is the control source (it’s called gridhelper.cs)! (In theory, it should work for .NET 2.0 up to 3.5) // if you use this code, please leave the original author //author: Egbert Nierop using System; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; namespace adccure.tools { public sealed class GridView2 : GridView { public GridView2() : base() { } private int _pageCount; public override int PageIndex { get { object o = ViewState["pgi"]; return o == null ? 0 : (int)o; } set { ViewState["pgi"] = value; } } public override int PageCount { get { return _pageCount; } } public void PageCountSet(int pageCount) { _pageCount = pageCount; } } public sealed class CustomPager : ITemplate { readonly Table tbl; readonly GridView2 _grid; //readonly int _totalPages; public CustomPager(GridView2 grid, int totalPages) { tbl = new Table(); grid.PageCountSet(totalPages); tbl.Width = Unit.Percentage(100); _grid = grid; } void ITemplate.InstantiateIn(Control container) { container.Controls.Add(tbl); int pageSize = _grid.PageSize; int pageIndex = _grid.PageIndex; int pageButtonCount = _grid.PagerSettings.PageButtonCount; int pageCount = _grid.PageCount; ClientScriptManager cs = _grid.Page.ClientScript; _grid.PagerSettings.Visible = true; var trow = new TableRow(); var trowpagePosition = new TableRow(); tbl.Rows.Add(trow); tbl.Rows.Add(trowpagePosition); TextBox tb = new TextBox(); tb.ID = "txtJumpPage"; tb.MaxLength = 4; tb.Width = Unit.Pixel(40); tb.Text = (pageIndex + 1).ToString(); //avoid bubble up by return false tb.Attributes["onkeydown"] = string.Format("if (event.keyCode==13) {__doPostBack('{0}', 'Page$' + this.value ); return false;}", _grid.UniqueID); LiteralControl lit = new LiteralControl(string.Format(" of {0}", (pageCount + 1).ToString())); TableCell posCaption = new TableCell(); trowpagePosition.Cells.Add(posCaption); posCaption.Controls.Add(tb); posCaption.Controls.Add(lit); int cellspan = 0; if (pageIndex > 0) { var cellText = new TableCell(); trow.Cells.Add(cellText); cellspan++; cellText.Controls.Add(new HyperLink() { NavigateUrl = cs.GetPostBackClientHyperlink(_grid, string.Format("Page${0}", pageIndex - pageButtonCount >= 0 ? (pageIndex - pageButtonCount) + 1 : 1), false), Text = "<" }); } for (int x = pageIndex; x < pageIndex + pageButtonCount && x <= pageCount; x++) { var cellText = new TableCell(); cellspan++; trow.Cells.Add(cellText); cellText.Controls.Add(new HyperLink() { NavigateUrl = cs.GetPostBackClientHyperlink(_grid, string.Format("Page${0}", x + 1), false), Text = (x + 1).ToString(), }); } if (pageIndex + pageButtonCount < pageCount) { var cellText = new TableCell(); cellspan++; trow.Cells.Add(cellText); cellText.Controls.Add(new HyperLink() { NavigateUrl = cs.GetPostBackClientHyperlink(_grid, string.Format("Page${0}", (pageIndex + pageButtonCount) + 1), false), Text = ">" }); } tbl.Visible = true; posCaption.HorizontalAlign = HorizontalAlign.Center; posCaption.ColumnSpan = cellspan; } } } Now, I don’t publish the code to read from sample data (such as northwind), it would be a yadda, yadda and you know the drill. (In my code, it is just a silly HttpReferrer table having all the columns which allow you research this specific statistical interest of your web site) But in my datalayer, I have things like shown below. It is a great solution for those tables, for say, less than 100,000 records. SQL server is able to deal with these types of queries pretty well and we still avoid pumping around lots of redundant data on the network and gridview control.
public IList<HttpReferrer> getHttpReferrers(int pPage, int pPageSize, HttpReferrerSortOrder sortOrder, SortDirection sortDirection, out int totalRecords) { totalRecords = dcd.HttpReferrers.Count(); IQueryable<HttpReferrer> retVal = null; if (sortDirection == SortDirection.Ascending) { switch (sortOrder) { case HttpReferrerSortOrder.Referer: retVal = dcd.HttpReferrers.OrderBy(t => t.Referrer); break; case HttpReferrerSortOrder.IP: retVal = dcd.HttpReferrers.OrderBy(t => t.IP_Address); break; case HttpReferrerSortOrder.Page: retVal = dcd.HttpReferrers.OrderBy(t => t.page); break; default: retVal = dcd.HttpReferrers.OrderBy(t => t.ts); break; } } else { switch (sortOrder) { case HttpReferrerSortOrder.Referer: retVal = dcd.HttpReferrers.OrderByDescending(t => t.Referrer); break; case HttpReferrerSortOrder.IP: retVal = dcd.HttpReferrers.OrderByDescending(t => t.IP_Address); break; case HttpReferrerSortOrder.Page: retVal = dcd.HttpReferrers.OrderByDescending(t => t.page); break; default: retVal = dcd.HttpReferrers.OrderByDescending(t => t.ts); break; } } return retVal.Skip(pPage * pPageSize).Take(pPageSize).ToList(); } So, our Grid, can sort and it can page. How do we deal with the paging event at the code behind the aspx? It’s so simple! void gridHttpReferrers_PageIndexChanging(object sender, GridViewPageEventArgs e) { gridHttpReferrers.PageIndex = e.NewPageIndex; gridHttpReferrers.DataBind(); } void gridHttpReferrers_DataBinding(object sender, EventArgs e) { int totalRecords; int pageSize = gridHttpReferrers.PageSize; int pageIndex = gridHttpReferrers.PageIndex; gridHttpReferrers.DataSource = datadal.getHttpReferrers(pageIndex, pageSize, SortOrder, SortDir, out totalRecords); gridHttpReferrers.PagerTemplate = new CustomPager(gridHttpReferrers, totalRecords); gridHttpReferrers.PageCountSet (totalRecords / pageSize); } What did I do to get the new grid behavior inside the ASPX? Just rename the tag from asp:GridView to ctrl:GridView2 and create the reference (or in web.config) <%@ Register TagPrefix="ctrl" Namespace="adccure.tools" %> <ctrl:GridView2 runat="server" emptydatatext="No data available." ID="gridHttpReferrers" AllowPaging="true" AllowSorting="True" Width ="100%" AutoGenerateColumns="false" PageSize="20" DataKeyNames="ID"><PagerSettings Position="Top" PageButtonCount="20" /> <Columns> ETC. So, I hope this code was quite enlightning for you and you can play with it and have fun.
|
-
Sometimes, especially for files running on external FTP servers, where file names are case sensitive, a file named myFILE.html, is not the same file as myfile.html in the same path! This function, can be used on an NTFS path for that purpose where File.Exists would fail, because it is case insensitive. (However, it does not enable you to have two files with just a different case in the same path) /// <summary> /// Checks existance of file using a case sensitive compare /// </summary> /// <param name="file">must be full filename</param> /// <returns></returns> static bool FileExists(string file) { string pathCheck = Path.GetDirectoryName(file); string filePart = Path.GetFileName(file); if (string.IsNullOrEmpty(pathCheck)) { throw new ArgumentException("The file must include a full path", file); } string[] checkFiles = Directory.GetFiles(pathCheck, filePart, SearchOption.TopDirectoryOnly); if (checkFiles != null && checkFiles.Length > 0) { //must be a binary compare return Path.GetFileName(checkFiles[0]) == filePart; } return false; }
|
-
I've seen people pulling the hair out for not getting this API workign for them. The API, even if impersonating the current user, returns error 1309. "An attempt has been made to operate on an impersonation token by a thread that is not currently impersonating a client."
The clue is that this API, (and this is not clearly documented on the MSDN) needs a duplicated handle.
Anyway, spare your hair, have fun with the code. B.t.w. You can hire me for smart code and research on components etc.. http://www.adccure.nl for contact.
[StructLayout(LayoutKind.Sequential)] internal struct GENERIC_MAPPING { internal uint GenericRead; internal uint GenericWrite; internal uint GenericExecute; internal uint GenericAll; } [DllImport("advapi32.dll", SetLastError = false)] static extern void MapGenericMask([In, MarshalAs(UnmanagedType.U4)] ref TokenAccessLevels AccessMask, [In] ref GENERIC_MAPPING map); [DllImport("advapi32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool DuplicateToken(IntPtr ExistingTokenHandle, [MarshalAs(UnmanagedType.U4)] TokenImpersonationLevel level, out int DuplicateTokenHandle);
[DllImport("advapi32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool AccessCheck( [MarshalAs(UnmanagedType.LPArray)] byte[] pSecurityDescriptor, IntPtr ClientToken, [MarshalAs(UnmanagedType.U4)] TokenAccessLevels accessmask, [In] ref GENERIC_MAPPING GenericMapping, IntPtr PrivilegeSet, ref int PrivilegeSetLength, out uint GrantedAccess, [MarshalAs(UnmanagedType.Bool)] out bool AccessStatus);
[DllImport("kernel32")] static extern void CloseHandle(IntPtr ptr); internal static bool hasReadAccess(string path) {
// Obtain the authenticated user's Identity WindowsIdentity winId = WindowsIdentity.GetCurrent(TokenAccessLevels.Duplicate | TokenAccessLevels.Query); WindowsImpersonationContext ctx = null; int statError = 0; IntPtr dupToken = IntPtr.Zero; try { // Start impersonating //ctx = winId.Impersonate(); works but AccessCheck does not like this int outPtr; //AccessCheck needs a duplicated token! DuplicateToken(winId.Token, TokenImpersonationLevel.Impersonation, out outPtr); dupToken = new IntPtr(outPtr); ctx = WindowsIdentity.Impersonate(dupToken);
Folder.GENERIC_MAPPING map = new Folder.GENERIC_MAPPING(); map.GenericRead = 0x80000000; map.GenericWrite = 0x40000000; map.GenericExecute = 0x20000000; map.GenericAll = 0x10000000; TokenAccessLevels required = TokenAccessLevels.Query | TokenAccessLevels.Read | TokenAccessLevels.AssignPrimary | (TokenAccessLevels)0x00100000; // add synchronization MapGenericMask(ref required, ref map); uint status = 0; bool accesStatus = false;
// dummy area the size should be 20 we don't do anything with it int sizeps = 20; IntPtr ps = Marshal.AllocCoTaskMem(sizeps); //AccessControlSections.Owner | AccessControlSections.Group MUST be included, //otherwise the descriptor would be seen with ERROR 1338 var ACE = Directory.GetAccessControl(path, AccessControlSections.Access | AccessControlSections.Owner | AccessControlSections.Group); bool success = AccessCheck(ACE.GetSecurityDescriptorBinaryForm(), dupToken, required, ref map, ps, ref sizeps, out status, out accesStatus); Marshal.FreeCoTaskMem(ps); if (!success) { statError = Marshal.GetLastWin32Error(); } else { return accesStatus; } } // Prevent exceptions from propagating catch (Exception ex) { Trace.Write(ex.Message); } finally { // Revert impersonation if (ctx != null) ctx.Undo(); CloseHandle(dupToken); } if (statError != 0) { throw new Win32Exception(statError); } return false; }
This code is just a cut and paste. You can make it pretty.
|
-
Just for educational purpose (as for myself as well :) ) I post this code. Using this, a programmer can use a good practise, that is to use the culture information which is built in, into .NET instead of making that data him/herself.
based upon the following element in web.Config, the list will fill using the correct number of monthnames. It also keeps track of calendars, that have 13 months in some cultures. <globalization uiCulture="nl-nl"/>
using System; using System.Collections.Generic; using System.Web; using System.Linq; using System.Web.UI; using System.Web.UI.WebControls; using System.Threading; using System.Globalization; public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { int monthNumber = 0;
CultureInfo ci = Thread.CurrentThread.CurrentUICulture; var myMonthnames = ci.DateTimeFormat.MonthNames .Take(ci.Calendar.GetMonthsInYear(DateTime.Today.Year)).Select(p => new { monthNo = ++monthNumber, monthName = p });
ddlMonthnames.DataTextField = "monthName"; ddlMonthnames.DataValueField = "monthNo"; ddlMonthnames.DataSource = myMonthnames; ddlMonthnames.DataBind(); } }
<select name="ddlMonthnames" id="ddlMonthnames"> <option value="1">januari</option> <option value="2">februari</option> <option value="3">maart</option> <option value="4">april</option> <option value="5">mei</option> <option value="6">juni</option> <option value="7">juli</option> <option value="8">augustus</option> <option value="9">september</option> <option value="10">oktober</option> <option value="11">november</option> <option value="12">december</option> </select>
|
-
I was wondering if writing well known C# (or VB.NET if you wish) code flow statements, such as for .. and foreach etc. are faster or slower compared to a generic expression. The results are refreshing . At least using this simple array iteration. Array Iterations on millions of elements, of course, are not the the real life CPU eaters for an average ASP.NET website (eg), but consider this code. There are three loops, doing the same. (n.b.: I run a dual core i7200 CPU machine on Vista x64) int ctr = 0; var values = new string[1000000]. Select(p => p = (ctr++). ToString()). ToArray(); List<int> intList; intList = values.Select(p => int.Parse(p)).ToList(); int[] test1, test2, test3; // loop 10 times and calculate the average test1 = new int[10]; test2 = new int[10]; test3 = new int[10]; for (int zz = 0; zz < 10; zz++) {
// our millisecond counter // it's ok to run this test several times to get an average score int start = Environment.TickCount;
// convert the numeric array back to an int array
intList = values.Select(p => int.Parse(p)).ToList(); test1[zz] = Environment.TickCount - start;
//now do the same but using a foreach iteration start = Environment.TickCount;
intList = new List<int>();
foreach (var p in values) { intList.Add(int.Parse(p)); }
test2[zz] = Environment.TickCount - start;
//do it a last time, but with a for{} iteration // theoretically a this should save us an enumerator. start = Environment.TickCount; intList = new List<int>();
int z = values.Length; for (int x = 0; x < z; x++) { intList.Add(int.Parse(values[x])); }
test3[zz] = Environment.TickCount - start;
Console.WriteLine("{0}, {1}, {2}", test1[zz], test2[zz], test3[zz]); } Console.WriteLine("{0}, {1}, {2}", test1.Average(), test2.Average(), test3.Average()); Console.ReadKey(); To test this, run this code in release mode (plus Ctrl-F5, in Visual Studio). x64 CPU platform results: Test 1) 175ms Test 2) 154 ms Test 3) 155 ms x86 CPU platform results: Test 1) 198 ms Test 2) 161 ms Test 3) 169 ms Test 1 uses an generic expression to 'cast' a numeric string array to a List of type Int32. Cute line, isn't it? But unfortunately, as has been said, the LINQ expression, still is a bit slower than the non-linq versions.
How much slower when we deal with integer math and avoid any parsing overhead? Now if we replace the int.Parse() statement by a silly integer operation, such as intList = intvalues.Select(p => p -1 ).ToList(); we have to increase the loopcount to 10,000,000 to get the workload to any significance. Now we measure plain LINQ performance.
x64 results: Test 1) 269 ms Test 2) 125 ms Test 3) 128 ms
x86 results: Test 1) 276 ms Test 2) 126 ms Test 3) 121 ms
Conclusion: I expect over time that compilers become more and more smarter and can optimize even better LINQ expressions. However, as we saw in the first example, int.Parse already flattened the results and the relative slowness of LINQ greatly. Parsing and converting data in loops, is something we constantly do when we deal with XML and databases. So when the workload within the loop increases, the overhead of LINQ expressions quickly become no important factor.
So, for shorter code, I would not hesitate to use those expressions as in Test 1. In a real life business application, the performance of loops really does not determine the final user experience. It is (e.g.) how we access a database or other resources, such as XML. It would be another story, if we e.g. were doing 3D math animations, where C++ would be an obvious choice.
|
-
Na een lange tijd van afwezigheid, je kent het wel geen zin en geen tijd maar ook er geen tijd voor vrij willen maken.
Eigenlijk vind ik het toch belangrijk dat meer mede programmeurs of hoe ze zichzelf ook noemen. Kunnen genieten van de code die al is bedacht. Zodat het wiel niet opnieuw wordt uitgevonden, hooguit verbeterd.
Dus ik ga zo nu en dan weer schrijven en ik verwacht dan ook weer commentaar terug.
Het moet wel bi-directioneel zijn!!
Hans
|
-
Na enig puzzelen ben ik de volgende code gaan gebruiken.
Als enum de maanden van het jaar
public enum Maanden
{
januari,
februari,
maart,
april,
mei,
juni,
juli,
augustus,
september,
oktober,
november,
december
}
Het vullen van de DropDownList (ddl)
private void FillCombo()
{
Hashtable ht = GetEnumForBind(typeof(Utilities.Maanden));
ddl.DataSource = ht;
ddl.DataTextField = "value";
ddl.DataValueField = "key";
ddl.DataBind();
ddl.SelectedIndex = 0;
}
///
/// Get the enumration
///
/// TypeOf( maanden )
/// a hashtable
private Hashtable GetEnumForBind(Type enumeration)
{
string[] names = Enum.GetNames(enumeration);
Array values = Enum.GetValues(enumeration);
Hashtable ht = new Hashtable();
for (int i = 0; i
{
ht.Add(Convert.ToInt32(values.GetValue(i)).ToString(), names );
}
return ht;
}
Even simpel als het is.
Bedenk wel dat er in een enumeration geen spatie mogen woden gebruikt.
Dus items zo als "alle werkdagen" is niet toegestaan.
Om iemand te citeren ;-)
Happy coding
|
-
-
LINQ! Yes, Also I fell in love with linq. So here is my first try. And as you fans know, I like to dig into subjects. Maybe, the title is a little incorrect, but I wanted a query that returns one record, using a subquery in one statement! Using SQL syntax, this would look like the query just below.: Yes, It seems I can dream SQL, but it was shocking to see how I underestimated the LINQ syntax which took some extra hears from my head. The query returns events, that have not already been booked full. (There are still some places left) SELECT [t0].[id], [t0].[maxNumberofParticipants], [t0].[OwerId], [t0].[Description], [t0].[StartTime], [t0].[EndTime], [t0].[orderId] FROM [dbo].[Activity] AS [t0] WHERE ([t0].[OwerId] = @p0) AND ([t0].[maxNumberofParticipants] > @p1) AND ([t0].[maxNumberofParticipants] > (ISNULL(( SELECT SUM([t2].[value]) FROM ( SELECT [t1].[countOfPersons] AS [value], [t1].[activityId] FROM [dbo].[ActivityParticipant] AS [t1] ) AS [t2] WHERE [t2].[activityId] = [t0].[id] ),0))) I could make this a stored proc, and execute this easily. But that was in my previous life. So, how to express this using a LINQ query? The trick with the ISNULL function is to -cast- the countOfPersons field, to a nullable type! Since the GetValueOrDefault() is available only to nullable data types we must cast it to an int? datatype. The LINQ provider, will translate it finally, when it sends the actual SQL to SQL Server using TSQL function 'COALESC'. bt.w., it will not use the ISNULL function. public IEnumerable<Activity> getActivityParticipableByOwnerWith(Guid ownerGuid) { var query = from a in Activities where a.OwerId == ownerGuid && a.maxNumberofParticipants > 0 && a.maxNumberofParticipants > ActivityParticipants.Where(aid => aid.activityId == a.id).Sum(aid => (int?)aid.countOfPersons).GetValueOrDefault(0) select a ; return query; } Kuddo's to Khaled Moawad, who very patiently helped me to improve the syntax. b.t.w. guys/girls. Always try it hard yourself, before you ask the global community for support. That makes your brains retain better :) My next goal is to make LINQ like a natural language for me, like SQL was :)
|
-
I thought that the subject ‘SafeArrays’ were really from the previous age and there was really nothing new about them, I could tell you , that was not already known. So sit back and listen, what I am going to tell you, has been decided, in a dark hole, were some Microsoft Nerds must have thought that our programmers' live was too easy. So they modified the way the old variant arrays from the COM era were stored in memory. Which Windows editions are involved in this modification? I don't know exactly. Anyway, Windows 64 bit edition (x64) really is subject to this, while Windows 2003 x86 (32 bit) is not. And don't worry too much, if you only use Ole Automation API calls, and not like I did, directly manipulated the SAFEARRAY storage area, your hard working code at your customers site, still should work  So, let’s figure what has changed by looking at the code below which very common. SAFEARRAYBOUND bounds[1]; bounds.cElements = 10; bounds.lLbound = 0; SAFEARRAY *psa = SafeArrayCreate(VT_VARIANT, 1, &bounds); This creates a variant array, that is compatible with scripting, similar to Redim myArray(9) ' 0 to 9 is ten elements. But before the array storage memory layout modification that I'm talking about, LONG elSize = SafeArrayGetElementSize(psa); would return 16 (since sizeof(VARIANT) obviously equals that) but on Windows x64, this returns 24. Possibly, the alignment still is 16, but this makes me suspicious. Why did Microsoft change this? So I want to be sure it works independently of the Windows version or future features. // code below gives an impression how to loop through an ‘unknonn dimension sized Safe Array, in this case the variant type is VT_VARIANT. Don’t use it. It’s faulty now on modern Windows systems. LONG lElements = 0; for (LONG x = 0; x < cDims; x++) { SafeArrayGetLBound(psa, x + 1, &lbound); SafeArrayGetUBound(psa, x + 1, &ubound); lElements += (ubound - lbound + 1); } hr = SafeArrayAccessData(psa, &psadata); if (hr == S_OK) { VARIANT *myvarray = static_cast<VARIANT *>(psadata); for (LONG els = 0; els < lElements && hr == S_OK; els++) { // do something easy with the element VARTYPE vt = myvarray[els].vt; } SafeArrayUnaccessData(psa); } The code above, would work to go through all elements for –any type of-SafeArray, VT_UI1, VT_BSTR, VT_VARIANT, because we assumed, for instance, that a VT_UI1, would be one byte long, isn’t it? And a BSTR Safe Array, would be a memory block, of BSTR pointers. And a VARIANT array, would have been a block of elements, consisting of VARIANTS. So the code above, just worked, and looks in fact, pretty simple, fast and elegant. Why so? Because it avoided the task to construct the indices one by one, and to fetch the element by using SafeArrayGetElement(psa, rgindices, outputvalue). This one surely works, but has two important disadvantages, 1) when you deal with a known 3D or 2D sized-array, it's easy to fill the rgIndices in a loop, but if the dimensions are unknown, it would be required to iterate through the elements by other means. So you’re up to do some nasa stuff! J. 2) SafeArrayGetElement is relatively slow, since it copies the out value and you need to release the output (such as variants and strings). It’s like pumping around lots of memory, especially when the array is huge.
To get this working, I had to refresh my knowledge of some informatics school I did in the past, about simple math, about carry over on signed numbers. Since SafeArrays have different lowerbounds and upperbound ranges per dimension, you cannot simply multiply by a known row size, to initialize it. But anyway, to make a longer story shorter, and I might lack the correct choice of words, for things that I technically just ‘see’, I wrote the code for you. It’s pretty cool, I could not find such code through Google. J Some tips. I decided to avoid reading the pointer to the safearray descriptor at all, because I saw some unexplainable swapping of rgsaBounds. Better use the API if there is one! // assume psa to be of SAFEARRAY and this code is using ATL version 8 LONG cDims = SafeArrayGetDim(psa); //we ‘rebuild’ the bounds by using the API, not by reading the psa structure directly to avoid unclear behaviour CTempBuffer<SAFEARRAYBOUND> psaBound(cDims); CTempBuffer<LONG> rgIndices(cDims);
LONG dimPointer = 0; // our dimension pointer, we go from left to right to build up the rgIndices LONG currentEl = 0, ubound, lbound; for (LONG x = 0; x < cDims; x++) { SafeArrayGetLBound(psa, x + 1, &lbound); SafeArrayGetUBound(psa, x + 1, &ubound); psaBound[x].cElements = ubound - lbound + 1; psaBound[x].lLbound = rgIndices[x] = lbound; } // locking is not optional, it is needed. SafeArrayLock(psa); for(;;) //termination is decided within the loop { if (rgIndices[dimPointer] < (LONG)psaBound[dimPointer].cElements + psaBound[dimPointer].lLbound) { VARIANT* pVar ; // use the fast version instead of SafeArrayGetElement! hr = SafeArrayPtrOfIndex(psa, rgIndices, (void**)&pVar); if (FAILED(hr)) MYBAILOUT(hr); rgIndices[dimPointer]++; //this terminates the for as soon as we reached the last array element if (++currentEl == lElements) break; } // our carry on overflow stuff goes from left to right else { while(rgIndices[++dimPointer]++ == (LONG)psaBound[dimPointer].cElements + psaBound[dimPointer].lLbound) { } //reset previous cols to initial lowerbound from left to // most right carry position for (LONG z = 0; z < dimPointer; z++) rgIndices = psaBound .lLbound; // if carry has been done, we start counting on left again dimPointer= 0; } }
To visualise this, I think it’s usefull to dry test this using a matrix. Imagine, we have a script, that was created by your customer, in VBSCript and youre superpower, martian CPP needs to do something with this data. So, the Customer code could be: Redim MyArray(3,2) How would our dynamic rgIndice be iterated? rgIndices would follow this pattern. Left dimension | Right dimension | Absolute element position | 0 | 0 | 0 | 1 | 0 | 1 | 2 | 0 | 2 | 3 | 0 | 3 | 0 | 1 | 4 | 1 | 1 | 5 | 2 | 1 | 6 | 3 | 1 | 7 | 0 | 2 | 8 | 1 | 2 | 9 | 2 | 2 | 10 | 3 | 2 | 11 |
|
|
|
Of course, the algorithm, would go through any array, with any dimension. Now have much fun with this code, if you needed it in your code. Don’t forget to add error handling which I left out to keep it short. And sure, you could write some C# stuff, for your COM interop to deal with array marshaling as well. If this code was very useful for you, don’t forget to pay a visit to a component that uses this handy trick at http://www.nieropwebconsult.nl/asp_session_manager.htm
|
-
Imagine, you have written a succesfull gateway to say, the European Community to check the validity of a VAT number. It always works, but suddenly it does not work anymore. What's up? Could it be that some service pack modified the behaviour of the HttpWebRequest (including DotNet 2.0?) Sure it is! see http://support.microsoft.com/kb/915599 This posting might spare you several hours. The idea of the Expect100Continue setting is, that postdata is sent to the webserver in a separate post sequence package. But not all web servers like IIS (sure I'm MVP ) are full featured so in this case, this feature was hard to detect, only by using Netmon, I could figure what the HttpWebRequest was doing under the .NET runtime hood. The Uri to the European VAT check is http://ec.europa.eu/taxation_customs/vies/ And it does a POST request to viesquer.do (the previous obsolete URL was http://europa.eu.int/comm/taxation_customs/vies/cgi-bin/viesquer) It had worked before because it just dit a GET request instead of a POST. The steps that I walk through normally to write such a gateway, - opening my browser
- Fill the form
- Start Netmon 2.1 (or higher)
- Post the data
- Stop Netmon and filter on the HTTP protocol.
Now it's up to you to emulate the postdata and be sure that the character encoding is according to the supported one. In my case, I assume that UTF-8 is the worldstandard (it should work for most sites). The code below is technically correct for the mentioned purpose but it performs no validation checking. If you wish more flexibility some more work has to be done. static bool doVatCheck(string vat, string state) {
string urlQuery = @"http://ec.europa.eu/taxation_customs/vies/viesquer.do";
//important! The WebLogic Server 8.1 SP3 clearly does not support IETF RFC 2616 Section 10.1.1. // so we disable the expect100 header and HTTP behaviour ServicePointManager.Expect100Continue = false; HttpWebRequest req; StreamReader read=null; Encoding enc = System.Text.Encoding.UTF8; MemoryStream sw = new MemoryStream(); advancePos(enc, sw, "ms"); //member state advancePos(sw, '='); advancePos(enc, sw, state); advancePos(sw, '&'); advancePos(enc, sw, "iso"); //same as member state, ISO country code advancePos(sw, '='); advancePos(enc, sw, state); advancePos(sw, '&'); advancePos(enc, sw, "vat"); // 12no VATno. Do not submit dots or spaces advancePos(sw, '='); advancePos(enc, sw, vat);
req = (HttpWebRequest)HttpWebRequest.Create(urlQuery); req.Method = "POST"; req.ContentType = "application/x-www-form-urlencoded"; // optional setting, UserAgent. A firewall could block if no browser is detected req.UserAgent = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)"; long contLen = req.ContentLength = sw.Position; Stream str = req.GetRequestStream(); str.Write(sw.GetBuffer(), 0, (int)contLen); str.Flush(); try { read = new StreamReader(req.GetResponse().GetResponseStream(), enc); string buf = read.ReadToEnd(); read.Close(); return buf.IndexOf("Yes, valid VAT number") >= 0; } catch (WebException ex) { //ex.st string errr = (urlQuery + ex.Message);
} return false;
} static void advancePos(Encoding enc, MemoryStream sw, string val) { byte[] buf = HttpUtility.UrlEncodeToBytes(val, enc); sw.Write(bufje, 0, buf.Length); }
static void advancePos(MemoryStream sw, char val) { sw.WriteByte((byte)val); }
|
-
I'm glad to see, that MS still did not get rid of ATL and continued to support us, that's those guys, who think that .NET is really awful, but not the holy grail :) If you want to see it yourselfs, get a copy of windiff.exe or just run it, and compare your directories. It's a very handy tool, to compare source codes! So, I compared some features on ATL. Quickly I found some differences in atlcomcli.h (I did not check everything however) and the improvements are OK! Another part of the ATL source code, seems related to HTML and internet (ISAPI). Lots of checks and code has been added to make sure your precious HTML / encoding etc is kept in tact. Well, which improvements? As you might know that ATL is all, mainly about writing non-UI unmanaged code, such as COM components or using powerfull small sized wrappers around kernel-items like security, string management, etc. At first, finally, I found that MS recognized that SafeArrayGetVartype is flawed, since it does not really fetch the VARTYPE of the array in all situations. So they wrote AtlSafeArrayGetActualVartype. Another things is CComVariant::Clear() which was a dangerous function to use! Why and when? Because the function it wraps, is VariantClear and variantClear does only set vt to VT_EMPTY, but it does not 'zero' the rest of the VARIANT struct. Some programs, do expect in/out variables, and in the case that it is not always testing for the vt (variant type) and reallocates the CComVariant instance, it might erronously reallocate 'freed' or 'zombie'-pointers. So my advise is to never use CComVariant::Clear() but use CComVariant::ClearToZero() now to avoid a case of reallocating zombie data! Another handy improvements is a new overloaded method on the WriteToStream member function. It is CComVariant::WriteToStream(IStream *pstr, VARTYPE vt); So, if you write the current variant to a certain stream, you can convert it to a BSTR (for instance) using this extra new parameter.. Then we got some important bugfixes in atldb.h and atldbcli.h some annotation and datatype and even template fixes. Lets see atlutil.h. Lots of new helper functions have been added. AtlStrToNum and functions that help to convert and to support the conversion to and from __int64 (LONGLONG) datatype. Normally, I always had to lookup for such functions into the VC function library and to add some wrappers around them. Once I checked this all, and I recompiled one of my CPP, ATL projects, the size became just slightly bigger. But who cares? If this is all about more robust and safe code, I'm not disappointed by the Sp1 for VC on Visual Studio 2005. (I was disappointed, when MS deprecated a lot of syntax without any announcements since of Visual Studio 2003)
|
-
Toen ik 14 of 15 jaar oud was heb ik leren programmeren op een originele Philips P2000. Goh, het zoeken naar een link voor dit apparaat brengt weer wat herinneringen naar boven. Later kochten mijn ouders een Commodore 64, geheel tegen mijn zin in overigens, want ik had tenslotte geleerd wat je met die P2000 kon. Dat is later wel weer goedgekomen. Al hobbyend heb ik sindsdien niet meer zonder computer gezeten. Om vervolgens qua opleiding toch maar heel wat anders te gaan doen. Ik ken weinig professionele ontwikkelaars die het niet leuk vinden om thuis ook wat te maken. En andersom, er zijn er ook die van hun hobby hun werk willen maken. Natuurlijk zijn er uitzonderingen. Maar die bevest... ach, je snapt het wel. Microsoft maakt het intussen wel erg makkelijk om hobbyende programmeurs aan het .NET Framework te laten ruiken. Zo is vorig jaar de reeks Visual Studio Express edities uitgekomen. Alhoewel in het begin werd gezegd dat ze slechts tijdelijk gratis beschikbaar waren, zijn ze dat nog steeds. Natuurlijk was het een leuke truc om te zeggen dat je er snel bij moet zijn, en dat het anders geld gaat kosten. Zo krijg je snel een flinke hoeveelheid mensen aan het downloaden en ach, als je het dan toch hebt staan is er weinig op tegen om er ook eens wat mee te proberen. Inmiddels is ook XNA Game Studio Express versie 1.0 verschenen, een ontwikkeltool om zelf games te ontwikkelen. Die spellen kun je bovendien op een XBox 360 draaien. Samen met de lancering van XNA Game Studio Express is ook een open bètaversie van Torque X van GarageGames vrijgegeven.  Afgelopen dinsdag heeft Microsoft vervolgens Robotics Studio, een software development kit voor het aansturen van robots, uitgebracht. De 1.0-versie is voor niet-commercieel gebruik gratis te downloaden van de Microsoft Robotics-site. Een groot aantal fabrikanten van robots en toebehoren bieden ondersteuning voor de Robotics Studio, en nemen deel aan het Microsoft Robotics Studio Partner Program. Het is ook de bedoeling van Microsoft een platform te bieden waarvoor third-partyontwikkelaars software kunnen schrijven. Met het uitbrengen van de SDK heeft Microsoft nog maar een eerste stap in de wereld van de robotica gezet. Intussen zijn er overeenkomsten gemaakt voor samenwerking met verschillende universiteiten, en is Microsoft een belangrijke sponsor van RoboCup 2007, het wereldkampioenschap voetbal voor robots. De interesse van Microsoft is overigens niet zo vreemd. Volgens analisten zal de roboticamarkt een explosieve groei doormaken, om binnen enkele jaren vele miljarden dollars te vertegenwoordigen. Volgens mij komen we de kerstdagen wel door.
|
-
In de reeks 'De Basis' van Pearson Education is nu ook het boek van Michiel van Otegem verschenen. Eerder dit jaar verschenen al boeken over Visual Basic 2005 en Visual C# 2005 van respectievelijk André Obelink en ondergetekende.  Wat deze boeken, naar mijn idee, uniek maakt ten opzichte van het ruime aanbod van boeken over deze onderwerpen is: - de auteurs zijn in het dagelijks leven ontwikkelaar, architect, ontwerper. D.w.z. we zitten met onze voeten in de modder en schrijven dus niet over een theoretische wereld waar alles mooi en zuiver is. De praktische ervaring druppelt door in deze boeken.
- de auteurs zijn al geruime tijd actief in de Nederlandse ontwikkelaarsgemeenschap, zoals dotNed, ASPNL en VBCentral. De gekozen onderwerpen zijn daarom niet in een torenkamertje bij elkaar verzonnen, maar zijn gebaseerd op allerlei discussies met andere ontwikkelaars.
- de boeken zijn in het Nederlands. Natuurlijk, veel ontwikkelaars alhier lezen net zo makkelijk Engels, maar we dromen ehr... denken zijn nog altijd in onze moedertaal. Voor de herkenning, herinnering en communicatie met anderen heeft het Nederlands dus toch een pré.
|
|
|
|