Jack @ ASP.NET

As a software engineer, I focus on .NET, especially asp.net, C#, WCF and so on, and I am also very interested in Search Engine Optimization.

Entries Tagged ‘string’

C# Code Function: Converting List of strings to string

=== Program that converts List (C#) ===

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<string> dogs = new List<string>(); // Create new list of strings
        dogs.Add("Aigi");                       // Add string 1
        dogs.Add("Spitz");                      // 2
        dogs.Add("Mastiff");                    // 3
        dogs.Add("Finnish Spitz");              // 4
        dogs.Add("Briard");                     // 5

        string dogCsv = string.Join(",", dogs.ToArray());
        Console.WriteLine(dogCsv);
    }
}

=== Output of the program ===

Aigi,Spitz,Mastiff,Finnish Spitz,Briard

Optional Parameters in C# 4.0

The definition of a method, constructor, indexer, or delegate can specify that its parameters are required or that they are optional. Any call must provide arguments for all required parameters, but can omit arguments for optional parameters.

Each optional parameter has a default value as part of its definition. If no argument is sent for that parameter, the default value is used. Default values must be constants.

Optional parameters are defined at the end of the parameter list, after any required parameters. If the caller provides an argument for any one of a succession of optional parameters, it must provide arguments for all preceding optional parameters. Comma-separated gaps in the argument list are not supported. For example, in the following code, instance method ExampleMethod is defined with one required and two optional parameters.

E.g.

public void ExampleMethod(int required, string optionalstr = "default string", int optionalint = 10)

The following call to ExampleMethod causes a compiler error, because an  argument is provided for the third parameter but not for the second.

//anExample.ExampleMethod(3,  ,4);

However, if you  know the name of the third parameter, you can use a named argument to  accomplish the task.

anExample.ExampleMethod(3, optionalint: 4);

IntelliSense uses brackets to  indicate optional parameters, as shown in the following illustration.
Optional parameters in ExampleMethod
Consider a standard scenario with method overloading or constructor chaining.  In C# we’d have several methods with different signatures where, in effect, we’re really just after default values. Let’s take the scenario where we’ve got a little helper class to send emails in our application. In some cases we want to CC the administrator to troubleshoot issues; in some cases we want rich HTML emails rather than plain text. We might set up our methods like this: 1:  public void SendMail(string toAddress, string bodyText) 2:  { 3:      this.SendMail(toAddress, bodyText, true); 4:  } 5:    6:  public void SendMail(string toAddress, string bodyText, bool ccAdministrator) 7:  { 8:      this.SendMail(toAddress, bodyText, ccAdministrator, false); 9:  } 10:    11:  public void SendMail(string toAddress, string bodyText, bool ccAdministrator, bool isBodyHtml) 12:  { 13:      // Full implementation here 14:  } This is pretty standard method overloading and we essentially are setting default values (true for CC the Admin, and false for HTML emails). With C# 4.0 we can now make the code more concise by only having to implement 1 method: 1:  public void SendMail(string toAddress, string bodyText, bool ccAdministrator = true, bool isBodyHtml = false) 2:  { 3:      // Full implementation here 4:  } However, you do have to take into account your scenario.  If you have a situation where you actually need to know if the consuming code provided a value then this isn’t a good option because if “true” comes in for the 3rd parameter, you don’t know if the consuming code actually set this explicitly or if it was simply the result of the default value.  But in typical scenarios like this, it’s not a big deal.  Cracking open Reflector and looking at the IL that the C# compiler is generating: 1:  .method public hidebysig instance void SendMail(string toAddress, string bodyText, [opt] bool ccAdministrator, [opt] bool isBodyHtml) cil managed 2:  { 3:      .param [3] = bool(true) 4:      .param [4] = bool(false) 5:      .maxstack 8 6:      L_0000: nop 7:      L_0001: ret 8:  } Which Reflectors translates to a C# equivalent of: 1:  public void SendMail(string toAddress, string bodyText, [Optional, DefaultParameterValue(true)] bool ccAdministrator, [Optional, DefaultParameterValue(false)] bool isBodyHtml) 2:  { 3:  }

Server Side Validation With ASP.NET MVC

aspnet_mvc When you’re using ASP.NET MVC, there’s no validation controls for you compared to ASP.NET WebForms. It is always important to validate data on the server as well as the client. Back when Dynamic Data was being developed, a set of attributes was created to help tell the Dynamic Data folks about validation and other metadata so they could create smart scaffolds. What this allows you to do is decorate your classes or properties with validation attributes. A project that is available on CodePlex gives you the power to inject this validation into your MVC project. The CodePlex project can be found here. The end results are you can decorate your classes with some of the following attributes to perform server side validation:

  • Required – a required field
  • StringLength – allows you to set the minimum and maximum length of a string
  • RegularExpression – performs regular expression validation

When you use this your validation classes will look like the following example:

Via C#

   1: public class EmployeeMetaData

   2: {

   3: [Required]

   4:       [StringLength(10, ErrorMessage="Given name cannot be more than 10 characters")]

   5:       public string GivenName { get; set; }

   6:  

   7:       [Required]

   8:       public string Surname { get; set; }

   9: }

Via VB

   1: Public Class EmployeeMetaData

   2: Private privateGivenName As String

   3: <Required, StringLength(10, ErrorMessage:="Given name cannot be more than 10 characters")> _

   4: Public Property GivenName() As String

   5:       Get

   6:             Return privateGivenName

   7:       End Get

   8:       Set(ByVal value As String)

   9:             privateGivenName = value

  10:       End Set

  11: End Property

  12:  

  13:        Private privateSurname As String

  14:        <Required> _

  15:        Public Property Surname() As String

  16:              Get

  17:                    Return privateSurname

  18:              End Get

  19:              Set(ByVal value As String)

  20:                    privateSurname = value

  21:              End Set

  22:        End Property

  23: End Class

How to Hack the ASP.NET Parser

The ASP.NET compliation system is pretty complex. There are all kinds of extensibility points, including BuildProviders, PageParserFilters, ControlBuilders, ExpressionBuilders and the list goes on. One unknown *feature* of the parser is it’s ability to generate code from something called an InstanceDescriptor.

The parser has a special way of dealing with ITemplate properties so if we try to do this:

<asp:Repeater ID="repeater" runat="server" ItemTemplate="~/MyUserControl.ascx">
</asp:Repeater>

It fails because the there is no way to convert the string “~/MyUserControl.ascx” into an ITemplate.

The parser uses the TypeConverter attribute defined on properties it parses to aid in the conversion.  Enter TypeDescriptionProvider. These complex beasts are used at the heart of all designers in Visual Studio. There are used for things like populating the property grid, and adding and removing properties dynamically, basically a general purpose metadata API (think of it as an abstraction on top of reflection).

VirtualPathTemplate

The code we are going to generate will instantiate a VirtualPathTemplate with a virtual path pointing to a user control on our site. Normally when you define a template in markup, a special type called CompiledTemplateBuilder (which points to a delegate that builds the template at runtime) is assigned to it. We want to replace a line of code that looks like this:

repeater.ItemTemplate = new CompiledTemplateBuilder(BuildTemplate);

to this

repeater.ItemTemplate = new VirtualPathTemplate("~/MyUserControl.ascx");

TypeDescriptionProvider

After overriding about 4 classes (TypeDescriptionProvider, CustomTypeDescriptor, PropertyDescriptor, and finally TypeConverter) we are able to control what happens when the parser asks, “can we convert “~/MyUserControl.ascx” to an ITemplate?”.

Here is the code for the TemplateTypeConverter:

public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) {
// Allow InstanceDescriptor so that the code gen engine can use it to generate the correct
// code for the ITemplate property
return destinationType == typeof(InstanceDescriptor) || _converter.CanConvertTo(context, destinationType);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) {
var descriptorProvider = value as IInstanceDescriptorProvider;
if (descriptorProvider != null) {
return descriptorProvider.Descriptor;
    }
return _converter.ConvertTo(context, culture, value, destinationType);
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) {
return sourceType == typeof(string) || _converter.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) {
string stringValue = value as string;
if (stringValue != null) {
if (stringValue.StartsWith("~/")) {                    
// Assume this is a virtual path and return the instance description provider
// for it
return new VirtualPathInstanceDescriptorProvider(stringValue);
        }                
    }
return _converter.ConvertFrom(context, culture, value);
}

First the parser asks the converter if it can convert from a string so we always say yes. In ConvertFrom we try to convert the value to a string and check if the path starts with “~/” in order to determine if it’s a virtual path. If it is a virtual path we return an object that knows how to get an InstanceDescriptor from the virtual path (VirtualPathInstanceDescriptorProvider). Now we have successfully parsed the control.

Next the code generator tries to generate code for the ITemplate property. The code generator will eventually ask if it can convert the object we returned earlier (VirtualPathInstanceDescriptorProvider) to an InstanceDescriptor. The implementation of VirtualPathInstanceDescriptorProvider returns an instance descriptor that wraps a constructor info for a custom template we are going to use:

internal class VirtualPathInstanceDescriptorProvider : IInstanceDescriptorProvider {
private string _virtualPath;
private static ConstructorInfo s_Constructor = GetConstructor();
private static ConstructorInfo GetConstructor() {
return typeof(VirtualPathTemplate).GetConstructor(new[] { typeof(string) });
    }
public InstanceDescriptor Descriptor {
get {
return new InstanceDescriptor(s_Constructor, new[] { _virtualPath });
        }
    }
public VirtualPathInstanceDescriptorProvider(string virtualPath) {            
        _virtualPath = virtualPath;
    }        
}

The code generation engine then generates the resulting code we wanted to specify above using the constructor info and virtual path.

At runtime we use BuildManager.CreateInstanceFromVirtualPath(“~/MyUserControl.ascx”) to create an instance of the user control and add it to the control’s collection and we’re done.

public class VirtualPathTemplate : ITemplate {
private string _virtualPath;
public VirtualPathTemplate(string virtualPath) {
        _virtualPath = virtualPath;
    }
public void InstantiateIn(Control container) {
// Try to create the control from the virtual path
Control control = (Control)BuildManager.CreateInstanceFromVirtualPath(_virtualPath, typeof(Control));
if (control != null) {
// Add it to the controls collection
            container.Controls.Add(control);
        }            
    }
}