Supertext Home
Chief of the System Blog

Archive for the 'C#' Category


The Supertext REST API

Wednesday, February 22nd, 2012

What?

In the early days, the internet solely consisted of simple static webpages. With the Dot-Com boom (or bubble), the webpages changed into complex and dynamic applications. But they were all like islands. There was no connection whatsoever between them. But now, companies like Facebook and Twitter loosened up and enable other people to build applications, allowing to connect to them and add their own functionality.

Until now, Supertext has – besides some specific integrations – been an island too. But that’s over now. We have an open and public API allowing other applications to directly integrate with Supertext.

 

Why?

Who wants to integrate with Supertext you wonder? And why? Actually, lots of people. It started 4 years ago with Akero, a now defunct CMS system. Akero users were able to directly order translations and text editing from inside their CMS and got the final text delivered back into it. Clearly, this wasn’t enough to make Akero a success, but the need to order directly from 3rd party systems remained evident until today.

 

How?

Now it gets a bit technical. The Supertext API is built as a REST API that understands JSON or XML. Almost all modern open APIs are built alike. This means that they’re accessible with normal HTTP calls and they enable you to send and receive human readable JSON or XML messages.

 

Who?

Authentication is done via normal Basic HTTP Authentication. Most other APIs just use a token or your username and password. We decided to work with a username in combination with a custom token. You can get this token from your Supertext Account Settings page. If you don’t have a Supertext account, you can establish one on the sign up page.

 

Where?

Please send an e-mail to remyATsupertextDOTch and I will send you the URL for the sandbox and the live system.

 

Which?

Currently we support the functionality listed below, basically you can get quotes and make orders.

You can add the below parameter to most calls, to specify in what language you get the results back:

communicationlang={communicationlang}

Adding it is optional. Otherwise the response defaults to English.  We currently support CHF and EUR. For a quote you can choose between the two currencies, but if you create an order, whatever is set in your account will be used.

 

For the configuration

/translation/languagemapping/{language}

GET – No authentication necessary.

Helps you map a language in your system to one we support.

E.g. your CMS is setup for ‘de’ (German). We don’t actually translate into German, we translate into German for Switzerland, for Germany or for Austria. So this method returns you a list of possible matches. For {language} = ‘de’ the result would look like this:

{
  "Languages":
  [
    {
      "Iso":"de-CH",
      "Name":"German (CH)"
    },
    {
      "Iso":"de-DE",
      "Name":"German (DE)"
    },
    {
      "Iso":"de-AT",
      "Name":"German (AT)"
    }
  ],
  "Supported":false
}

 

To get a quote

/translation/quote

POST – authentication optional

Getting a quote over the API works in pretty much the same way as if you were using our normal website to order a translation (give it a try). Send us the text and in return, you see a list of possible delivery deadlines and prices per service levels  (translation/adaptation). You can send us the content structured in groups and items, which is helpful if you have CMS that is built that way. E.g. a group could be a page, items are title, content, metatags and so on. The following is a possible JSON for this call:

{
  "ContentType":"text\/html",
  "Currency":"chf",
  "Groups":
  [
    {
      "GroupId":"Group1",
      "Items":
      [
        {
          "Content":"This is the content of group 1",
          "Id":"1"
        },
        {
          "Content":"This is more content  of group 1",
          "Id":"2"
        }
      ]
    },
    {
      "GroupId":"Group2",
      "Items":
      [
        {
          "Content":"This is the content  of group 2",
          "Id":"1"
        },
        {
          "Content":"This is more content  of group 2",
          "Id":"2"
        }
      ]
    }
  ],
  "SourceLang":"de-CH",
  "TargetLang":"en-US",
}

 

And this could be what you get in return:

 

{
  "Currency":"CHF",
  "WordCount": 123,
  "Options":
  [
    {
      "DeliveryOptions":
      [
        {
          "DeliveryDate":"2012-02-22T09:25:46.0000000Z",
          "DeliveryId":1,
          "Name":"6h",
          "Price":124
        },
        {
          "DeliveryDate":"2012-02-22T15:25:46.0000000Z0",
          "DeliveryId":2,
          "Name":"24h",
          "Price":110
        },
        {
          "DeliveryDate":"2012-02-23T15:25:46.0000000Z",
          "DeliveryId":3,
          "Name":"48h",
          "Price":96
        },
        {
          "DeliveryDate":"2012-02-24T15:25:46.0000000Z",
          "DeliveryId":4,
          "Name":"3 Days",
          "Price":82
        },
        {
          "DeliveryDate":"2012-02-28T15:25:46.0000000Z",
          "DeliveryId":5,
          "Name":"1 Week",
          "Price":69
        }
      ],
      "Description":"For an exact reproduction of the original text, the translated text is checked by a proofreader.",
      "Name":"Translation",
      "OrderTypeId":6,
      "ShortDescription":"4-eye principle."
    },
    {
      "DeliveryOptions":
      [
        {
          "DeliveryDate":"2012-02-22T15:25:46.0000000Z",
          "DeliveryId":2,
          "Name":"24h",
          "Price":254
        },
        {
          "DeliveryDate":"2012-02-23T15:25:46.0000000Z",
          "DeliveryId":3,
          "Name":"48h",
          "Price":222
        },
        {
          "DeliveryDate":"2012-02-24T15:25:46.0000000Z",
          "DeliveryId":4,
          "Name":"3 Days",
          "Price":190
        },
        {
          "DeliveryDate":"2012-02-28T15:25:46.0000000Z",
          "DeliveryId":5,
          "Name":"1 Week",
          "Price":159
        }
      ],
      "Description":"For the translation to sound as good as the original, the translated text is stylistically post-edited.",
      "Name":"Adaptation",
      "OrderTypeId":7,
      "ShortDescription":"6-eye principle."
    }
  ]
}

 

And to make an order

/translation/order

POST – authentication necessary

When making an order, we need more details than we need for a quote. At first, we need to know which quote you’ve chosen. So you have to add the OrderTypeId and the DeliveryId.

Very important is the CallbackUrl. After the translation job is finished, we will call this URL with a similar JSON order object and write back the translated content into your system.

We strongly advise you to use the following fields:

OrderName: Some short description about this order. E.g. “Spring sale”

ReferenceData: Anything you need to identify this order later (besides the GroupId and the Id for the Content field). And optionally, you could add some security token so that not everybody with access to the callback URL can update your system.

Referrer: Name of your website or system. E.g. Supertext US Website

 

{
  "CallbackUrl":"http://localhost:65346/API/ApiCallbackExample.aspx",
  "ContentType":"text\/html",
  "Currency":"chf",
  "DeliveryId":1,
  "OrderName":"Some title",  
  "AdditionalInformation":"Please make sure you always translate this like that.",
  "OrderTypeId":6,
  "ReferenceData":"NodeId:4ee69461-1c8d-4fbe-9d77-7d05e46bc4a8",
  "Referrer":"Supertext Magazin",
  "SourceLang":"de-CH",
  "TargetLang":"en-US",
  "WordCount":0,
  "Groups":
  [
    {
      "Context":"Some Node",
      "GroupId":"Group1",
      "Items":
      [
        {
          "Comment":null,
          "Content":"This is the content of group 1",
          "Context":null,
          "Id":"1"
        },
        {
          "Comment":null,
          "Content":"This is new content of group 1",
          "Context":null,
          "Id":"2"
        }
      ]
    },
    {
      "Context":"Some other Node",
      "GroupId":"Group2",
      "Items":
      [
        {
          "Comment":null,
          "Content":"This is the content of group 2",
          "Context":null,
          "Id":"1"
        },
        {
          "Comment":null,
          "Content":"This is new content of group 2",
          "Context":null,
          "Id":"2"
        }
      ]
    }
  ]
}

 

You get back another order object with the order Id, a price, order and delivery date. All the other data is just for your reference.

 

{
  "Id":12023,
  "OrderDate":"2012-02-09T13:43:46.0000000Z",
  "Deadline":"2012-02-09T13:43:46.0000000Z",
  "Price":199,
  "Currency":"chf",
  "DeliveryId":1,
  "OrderTitle":"Some title",
  "OrderTypeId":6,
  "ReferenceData":"NodeId:4ee69461-1c8d-4fbe-9d77-7d05e46bc4a8",
  "SourceLang":"de-CH",
  "TargetLang":"en-US",
  "Status":"New"
}

 

Status

/translation/order/{id}

GET – authentication necessary

In order to track the status of an order, just use the Id you got after you submitted the order. You will get an Order object like the one above as a return.

 

Callback
In order to get your translation back from us, you need to provide us with a callback URL.
When you create the order, you have to use the field CallbackUrl. This URL needs to accept a JSON of the type Order (as shown above). That Order object will contain your translation in the Groups and Item fields with the same Id’s, so you can map them back to your own datastructure.
We recommend that you use the ReferenceData field as some type of authentication. For example you could use a combination of an internal Id and the MD5 hash of this Id. We will return the ReferenceData field in our callback. You can then just check if the Hash matches to make sure nobody else is writing into your system.

 

Dates

All dates are in UTC and in the ISO_8601 format:

2012-05-03T12:09:46.0000000Z

If you are using .NET just use Convert.ToDateTime(), this will automatically convert from the UTC time to your local time.


Basic Authentication with WCF Web API Preview 6

Friday, February 3rd, 2012

UDATE:
I’ve built a new version for the MVC Web API:
http://remy.supertext.ch/2012/04/basic-http-authorization-for-web-api-in-mvc-4-beta/

One should not believe it, but it seems that there is no official way to use your own version of Basic HTTP Authentication with the WCF Web API in an MVC Web Application yet. So, now that I’ve used all possible necessary keywords we can dive right in.

 

We are using a custom ASP.NET Membership provider and the REST API should work with a token over Basic HTTP Authentication (like Basecamp). So, the built in Windows Basic Authentication is not an option.

After scanning dozens of posts on Stackoverflow and other resources I realized that either I have to go with the WCF REST Contrib library or with Open Rasta. But since I already started with the WCF Web API Preview 6 to build my REST API (and it worked fine so far) I didn’t want to switch now.

The most promising solution I found was from jslaybaugh. He is basically using a custom version of the [Authorize] attribute from the normal MVC framework and somehow integrated it all with Ninject. For some reason I didn’t got it working. In general AuthorizeAttribute and action filters are MVC specific. For WCF we have the HttpOperationHandler or the DelegatingHandler. They have their specific uses and advantages. Some info about his from Glenn Block.

I’ve decided to go with the HttpOperationHandler and found a good example from Phil Haack where he implements a Role authorization Module, that also works with Attributes, so we can implement something very similar to the AuthorizeAttribute of MVC. There are simpler solutions, e.g. you could just check this inside your Controller, but Craig Stuntz has some good points about why this is a bad idea.

So, let’s look at the code (which is a potpourri of all the above examples):

[AttributeUsage(AttributeTargets.Method)]
public class BasicHttpAuthorizationAttribute : Attribute
{
    bool requireSsl = true;
 
    public bool RequireSsl
    {
        get { return requireSsl; }
        set { requireSsl = value; }
    }
}

With this simple BasicHttpAuthorizationAttribute class we can achieve the the attribute functionality. So we can use it like this:

[BasicHttpAuthorization(RequireSsl = true)]
[WebGet(UriTemplate = "")]
public IEnumerable Get()
{

 

The RequireSsl is just an example property, you could also do a role membership check in the same way.

But the most important part is the implementation of the HttpOperationHandler. We pass the BasicHttpAuthorizationAttribute as an argument.

There are three main points that deserve attention here:

  1. If the user is not authenticated yet or provides the wrong credentials we return a HttpResponseException in the OnHandle method. We set the status code to 401 and add the WWW-Authenticate = Basic header. This creates the functionality, where the browser asks for a username/password and then automatically resends the request.
  2. In ParseAuthHeader we get the username and password out of the request. You can  then use this info with in your own way. For example with your own custom membership provider.
  3. If the user can access this method, we create a GenericPrincipal and assign it to HttpContext.Current.User. Afterwards you can then just use your normal MemberShip and RoleProvider like in every normal ASP.NET application.

Other than that, there is not much magic in here.

public class BasicHttpAuthorizationOperationHandler : HttpOperationHandler
{
 
    BasicHttpAuthorizationAttribute basicHttpAuthorizationAttribute;
 
    public BasicHttpAuthorizationOperationHandler(BasicHttpAuthorizationAttribute authorizeAttribute)
        : base("response")
    {
        basicHttpAuthorizationAttribute = authorizeAttribute;
    }
 
    protected override HttpRequestMessage OnHandle(HttpRequestMessage input)
    {
        if (Authenticate(input))
        {
            return input;
        }
        else
        {
            var challengeMessage = new HttpResponseMessage(HttpStatusCode.Unauthorized);
            challengeMessage.Headers.Add("WWW-Authenticate", "Basic");
            throw new HttpResponseException(challengeMessage);
        }
    }
 
    private bool Authenticate(HttpRequestMessage input)
    {
        if (basicHttpAuthorizationAttribute.RequireSsl && !HttpContext.Current.Request.IsSecureConnection && !HttpContext.Current.Request.IsLocal) return false;
 
        if (!HttpContext.Current.Request.Headers.AllKeys.Contains("Authorization")) return false;
 
        string authHeader =  HttpContext.Current.Request.Headers["Authorization"];
 
        IPrincipal principal;
        if (TryGetPrincipal(authHeader, out principal))
        {
            HttpContext.Current.User = principal;
            return true;
        }
        return false;
    }
 
    private bool TryGetPrincipal(string authHeader, out IPrincipal principal)
    {
        var creds = ParseAuthHeader(authHeader);
        if (creds != null)
        {
            if (TryGetPrincipal(creds[0], creds[1], out principal)) return true;
        }
 
        principal = null;
        return false;
    }
 
    private string[] ParseAuthHeader(string authHeader)
    {
        // Check this is a Basic Auth header
        if (authHeader == null || authHeader.Length == 0 || !authHeader.StartsWith("Basic")) return null;
 
        // Pull out the Credentials with are seperated by ':' and Base64 encoded
        string base64Credentials = authHeader.Substring(6);
        string[] credentials = Encoding.ASCII.GetString(Convert.FromBase64String(base64Credentials)).Split(new char[] { ':' });
 
        if (credentials.Length != 2 || string.IsNullOrEmpty(credentials[0]) || string.IsNullOrEmpty(credentials[0])) return null;
 
        // Okay this is the credentials
        return credentials;
    }
 
    private bool TryGetPrincipal(string userName, string password, out IPrincipal principal)
    {
        // this is the method that does the authentication
        // you can replace this with whatever logic you'd use, but proper separation would put the
 
        if (userName.Equals("remy@test.ch") && password.Equals("test"))
        {
            // once the user is verified, assign it to an IPrincipal with the identity name and applicable roles
            // Example:
            //principal = new GenericPrincipal(new GenericIdentity(userName), System.Web.Security.Roles.GetRolesForUser(userName));
 
            principal = new GenericPrincipal(new GenericIdentity(userName), new string[] {"Admin", "User"});
 
            return true;
        }
        else
        {
            principal = null;
            return false;
        }
    }
}

Last but not least, we need to hook up our HttpOperationHandler with the BasicHttpAuthorizationAttribute object and route. For this we create a custom WebApiConfiguration and use a class extension to do the wiring. Honestly, I’m not really sure what is going on here, but it works :-)

public class ApiConfiguration : WebApiConfiguration
{
    public ApiConfiguration()
    {
        EnableTestClient = true;
 
        RequestHandlers = (c, e, od) =>
        {
            // TODO: Configure request operation handlers
        };
 
        this.AppendAuthorizationRequestHandlers();
    }
}
 
public static class ConfigExtensions
{
    public static void AppendAuthorizationRequestHandlers(this WebApiConfiguration config)
    {
        var requestHandlers = config.RequestHandlers;
        config.RequestHandlers = (c, e, od) =>
        {
            if (requestHandlers != null)
            {
                requestHandlers(c, e, od); // Original request handler
            }
            var authorizeAttribute = od.Attributes.OfType()
              .FirstOrDefault();
            if (authorizeAttribute != null)
            {
                c.Add(new BasicHttpAuthorizationOperationHandler(authorizeAttribute));
            }
        };
    }
}

And we pass this configuration in the global.asax.cs to the route handler:

public static void RegisterRoutes(RouteCollection routes)
{
    var config = new ApiConfiguration();
 
    routes.Add(new ServiceRoute("example", new HttpServiceHostFactory() { Configuration = config }, typeof(ExampleAPI)));
 
}

That is it. You can download the example project here: BasicAuthenticationWithWcfWebAPI.zip

 

Please let me know if this works for you and specially, if you find ways to improve it.


A WPF project running from the CMD Prompt or as a Service and has a GUI

Thursday, November 3rd, 2011

Very often it’s necessary to run scheduled jobs or maintenance tasks once a day, once a month or even every few minutes. This can be easily achieved by creating your own Windows Service. For example if you want to send reminder e-mails to your customers. But wouldn’t it be nice to be able to also call the application from the command line so that it can do all the work and shut down afterwards? There is even a possibility of making a small script that calls all your similar applications, checks if any work has to be done, does the work and then shuts down. Also, you would like to make it possible to check for any work that needs to be done just by clicking on one button? Well, here you can find a very simple example on how all of this can be done.

First we need a class that is derived from ServiceBase. In that class need a public constructor, an OnStart and an OnStop method. For example, we want to make a service that does some usefull work every 4 minutes. An example of the code for the ServiceBase derived class is given next.

 
public class ServiceClass : ServiceBase
{
    protected System.Timers.Timer timer = new System.Timers.Timer(300000));
 
    /// <summary>
    /// Public constructor that initializes the service and
    /// sets its parameters like ServiceName.
    /// </summary>
    public ServiceClass()
    {
        CanPauseAndContinue = true;
        CanShutdown = true;
        ServiceName = "My Service Name";
    }
 
    /// <summary>
    /// Method specifies what code to execute each time a service
    /// is started. Here it configures parameters for the timer
    /// </summary>
    /// <param name="args"></param>
    protected override void OnStart(string[] args)
    {
        timer.Enabled = true;
        timer.AutoReset = true;
        timer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
    }
 
    /// <summary>
    /// Method specifies what code to execute each time a service
    /// is stopped. Here it configures parameters for the timer. 
    /// </summary>
    protected override void OnStop()
    {
        timer.Enabled = false;
    }
 
    /// <summary>
    /// Method specifies what code to execute each time a timer's
    /// interval is up. Here will be all the code that has to be done
    /// continuously. 
    /// </summary>
    /// <param name="source"></param>
    /// <param name="e"></param>
    private static void OnTimedEvent(object source, ElapsedEventArgs e)
    {
        //do some work
    }
}

We also need a class that extends the class Installer. In that class we need to define at least the class constructor method. There we create all the installer objects necessary to install the application as a service.

 
[RunInstaller(true)]
public class MyProjectInstaller : Installer
{
    private ServiceInstaller serviceInstaller;
    private ServiceProcessInstaller processInstaller;
 
    /// <summary>
    /// The public constructor that is executed when the
    /// installation is started.
    /// </summary>
    public MyProjectInstaller()
    {
        processInstaller = new ServiceProcessInstaller();
        serviceInstaller = new ServiceInstaller();
 
        processInstaller.Account = ServiceAccount.LocalSystem;
 
        serviceInstaller.StartType = ServiceStartMode.Automatic;
        serviceInstaller.ServiceName = "My Service Name";
 
        Installers.Add(serviceInstaller);
        Installers.Add(processInstaller);
    }   
}

The code given here is the minimal code needed for the service to be installed. In the class constructor we first need to instantiate the installer objects needed for the installation. One installer is an object from the class ServiceProcessInstaller and the other one is an object of the class ServiceInstaller. Then we set up all the information about the service – how will the service start (automatically), on what account will the service run(local system) and the name of the service (My Service Name). The name has to be the same as the name defined in the ServiceBase derived class. In the end we add the two created installers to the collection of installers.
If we want some additional work done on each install or uninstall, it is possible to override the methods OnBeforeInstall and OnBeforeUninstall. For example, if we want the service to be started only if there is a certain parameter, it is possible to add this code :

 
protected override void OnBeforeInstall(System.Collections.IDictionary savedState)
{
    Context.Parameters["assemblypath"] = "\"" + 
        Context.Parameters["assemblypath"] + "\" -service";
        base.OnBeforeInstall(savedState);
}
 
protected override void OnBeforeUninstall(System.Collections.IDictionary savedState)
{
    Context.Parameters["assemblypath"] = "\"" + 
        Context.Parameters["assemblypath"]   + "\" -service";
        base.OnBeforeUninstall(savedState);
}

After doing the work required for changing the call to the service, we call the same method from the base class in order to install the service correctly.
After creating the two classes necessary for defining a windows service, we have to define the main application activity. So, what we have to do is add code that will define when the application will be executed as a windows service and when it’ll just do the work once and shutdown.

public partial class App : Application
{
   protected override void OnStartup(StartupEventArgs e)
   {
       base.OnStartup(e);
       string[] commandLineArgs = System.Environment.GetCommandLineArgs();
 
       if(commandLineArgs.Length>1 && commandLineArgs[1].Equals("-someWork"))
       {
         //do some work
       }
 
       else if(commandLineArgs.Length>1 && commandLineArgs[1].Equals("-service")) 
       {
          ServiceBase.Run(new ServiceClass());
       }
       else
       {
          // do some work
       }  
    }
}

This way it is possible to manage for the application to do different work when it’s called in different ways. All the work that the application does (as the service or called from the command line) should be defined in a separate class.
In order to install the new service, open command prompt and type:

installutil ProjectName.exe

where project name is the name of the project with the windows service and it’s installer. In order to uninstall the service type :

installutil /u ProjectName.exe

And that’s it! Your service is ready to be used! Now if we want to be able to start the application with a user interface, it’s necessary to make some changes to the class App in the method OnStartup and we also have to define the class MainWindow which will contain the entire code that needs to be done through the user interface.
In the case described above, the OnStartup class will look similar to this :

public partial class App : Application
{
   protected override void OnStartup(StartupEventArgs e)
   {
       base.OnStartup(e);
       string[] commandLineArgs = System.Environment.GetCommandLineArgs();
 
       if(commandLineArgs.Length>1 && commandLineArgs[1].Equals("-someWork"))
       {
	   //starting the application with the appropriate command arguments will start 
	   //it in console mode
           MainWindow f = new MainWindow();
           f.ConsoleMode = true;
           //do some work
        }
        else if(commandLineArgs.Length>1 && commandLineArgs[1].Equals("-service")) 
        {
	    //start application as a service
            ServiceBase.Run(new ServiceClass());
        }
        else
        {
            //in case there were no command arguments, start the user interface and do    //some work
            MainWindow f = new MainWindow();
            f.ShowDialog();
            this.Shutdown();
        }            
    }
}

In the class Application there is a property ConsoleMode which is used to indicate whether or not the application mode of running is console mode. Property ConsoleMode is defined in the class that defines the user interface, MainWindow. If we are also using some of the methods defined in the class MainWindow even when we start the application from the command line, we have to find a way to indicate that the application should shut down after the work is done. That’s why in the class that describes the user interface we have to add this wherever we want to check if the application needs to be shut down:

if (ConsoleMode)
{
    Dispatcher.Invoke(new Action(() => { Application.Current.Shutdown(); }));
}

And that’s it! Now you have built your first application that can be used as a windows service, with a user interface or called from the command line.


Simple C# encryption and decryption

Thursday, January 20th, 2011

There are various ways to encrypt and decrypt a string in with .Net. The simplest is to use Base64. But this just makes the string unreadable for a human, but it is not a real encryption. Although, for most case is is likely enough.

If you want something a little fancier, I have a simple solution for you:

 

public static String Encrypt(string source)
{
   
DESCryptoServiceProvider des = new DESCryptoServiceProvider
();
   
byte
[] Key = { 12, 13, 14, 15, 16, 17, 18, 19 };
   
byte
[] IV =  { 12, 13, 14, 15, 16, 17, 18, 19 };

    ICryptoTransform encryptor = des.CreateEncryptor(Key, IV);

    try
    {
       
byte[] IDToBytes = ASCIIEncoding
.ASCII.GetBytes(source);
       
byte
[] encryptedID = encryptor.TransformFinalBlock(IDToBytes, 0, IDToBytes.Length);
       
return Convert
.ToBase64String(encryptedID);
    }
   
catch (FormatException
)
    {
       
return null
;
    }
   
catch (Exception
)
    {
       
throw
;
    }
}


public static string Decrypt(string
encrypted)
{
   
byte
[] Key = { 12, 13, 14, 15, 16, 17, 18, 19 };
   
byte
[] IV =  { 12, 13, 14, 15, 16, 17, 18, 19 };

    DESCryptoServiceProvider des = new DESCryptoServiceProvider();
   
ICryptoTransform
decryptor = des.CreateDecryptor(Key, IV);

    try
    {
       
byte[] encryptedIDToBytes = Convert
.FromBase64String(encrypted);
       
byte
[] IDToBytes = decryptor.TransformFinalBlock(encryptedIDToBytes, 0, encryptedIDToBytes.Length);
       
return ASCIIEncoding
.ASCII.GetString(IDToBytes);
    }
   
catch (FormatException
)
    {
       
return
null;
    }
   
catch (Exception
)
    {
       
throw
;
    }
}

Just make sure that you replace the Key and IV values.

I had to use the  Base64 Encoding in addition to the encryption so you can use the result string like a normal string and write it somewhere. Otherwise it could contain non ASCII characters and you couldn’t easily use it.

 

If you want to use the encrypted string in a url, you should HTML encode it too:

System.Web.HttpUtility.UrlEncode(Encrypt(mystring));
 

Have fun and let me know if you have any questions.


Collection was modified; enumeration operation may not execute

Wednesday, July 29th, 2009

Ever tried to remove or change something in a C# Enumeration while you iterate over it? Yes, it does not work very well.

But there are ways around it.

foreach (Item item in items.ToArray())
{
    if (item.Visible == false)
    {
        items.Remove(item);
    }
}

The simplest way is to use ToArray(). No much coding, no complicated loops, but obviously not very efficient for larger lists.

A few alternatives are presented by Kevin Ransom.


Using Log4Net for a Windows Console Application

Wednesday, February 11th, 2009

Most examples with Log4Net are with ASP.NET, but it works perfectly fine with Windows Form and Console Applications too.

What you need is an App.config file that you add to your project.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821" />
  </configSections>
  
    <!-- Log4net Logging Setup -->
  <log4net debug="false">
    <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
      <param name="File" value="C:\\directory\\supertext_outsidetasks.log"/>
      <param name="AppendToFile" value="true"/>
      <maxSizeRollBackups value="10"/>
      <datePattern value="yyyy-MM-dd"/>
      <rollingStyle value="Date"/>
      <lockingModel type="log4net.Appender.FileAppender+MinimalLock"/>
      <layout type="log4net.Layout.PatternLayout">
        <header value="Date | Level | SessionID | Logger | Message | &#xA;"/>
        <param name="ConversionPattern" value="%date{ABSOLUTE}| %-5p | %-30logger| %m|%n"/>
      </layout>
    </appender>
    <root>
      <priority value="DEBUG"/>
      <appender-ref ref="RollingLogFileAppender"/>
    </root>
  </log4net>
</configuration>

Then initialize it in your main() function:

using log4net;
using log4net.Config;


static void Main(string[] args)
{
    log4net.Config.XmlConfigurator.Configure();
    
    //your code
}

And you can use it like in ASP.NET:

log.Info("Hello Logfile");

The only issue I had was that adding the configSection like this did not work:

<configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"/>
</configSections>

I had to add the whole shebang with Version, etc. to it. Then it started working. Anyone else had that problem?

Anyway, are people still using Log4Net? There is not much activity on the project anymore and the last version is getting pretty old.

  • Topics
  • Archive
  • Subscribe
  • Facebook
  • Twitter