September 2009 - Posts

Jag skrev tidigare om hur man kan använda metadata på modellerna för att kunna anpassa renderingen av dessa på sidorna. Jag fick en intressant kommentar på det av användaren ”BlackMustard” på aspsidan som lyder:

men går inte detta lite stick i stäv med separation of concerns-principen? nu har jag ju plötsligt attribut i min model som bestämmer hur saker ska renderas!

Med Dynamic Data så går detta att lösa enkelt med en metadataklass, och då jag inte har sett någon dokumentation om detta för ASP.NET MVC 2 så antog jag att de inte hade implementerat det ännu för detta. Efter att ha testat det så insåg jag att det visst fungerade, vilket gör att man på ett bättre sätt kan sätta metadata utan att behöva ändra direkt på modellen.

Jag kommer att använda samma projekt som i den föregående artikeln, samt använda samma funktionalitet, men flytta ut attributen till en separat klass.

Det första vi behöver göra är att ändra på Customer-klassen så att den blir partial. Det gör att vi kan lägga till information på den utan att behöva ändra i original-klassen. Kör vi med till exempel Linq to Entities så är det en genererad klass och partial som default.

När vi har satt klassen som partial så ska vi skapa en ny fil (Customer_Metadata.cs), vilken kommer att innehålla vår metadata.

Det vi gör är att vi tar de properties vi vill modifiera i Customer-klassen och sätter attributen på dem. Sedan kan vi ta bort attributen från original-klassen.

Den partiella Customer-klass vi har skapat i den nya filen skall ha attributet ”MetadataType()”, vilken gör att vi kan sätta en klass som innehåller metadata för denna.

Den nya cs-filen ser då ut så här:

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
 
namespace Customers.Models
{
    [MetadataType(typeof(Customer_Metadata))]
    public partial class Customer { }
 
    public partial class Customer_Metadata
    {
        [DisplayName("Kund-ID")]
        public int CustomerId { get; set; }
 
        [UIHint("CapitalString")]
        public string FirstName { get; set; }
    }
}

Om vi kör sidan nu så ser vi att vi får exakt samma resultat som innan, utan att behöva ändra i original-Customer-klassen.

Vi kan på detta sätt enkelt separera på logiken som finns i modellen och den som används för presentationen av denna.

När man arbetar med data som inte ligger i objekt i minnet (vanliga POCO-collections) som t.ex. SQL så har man tidigare behövt skriva en SQL-sats som gör det man vill, vilket kan vara att hämta data, att lägga till data, att ta bort data samt att uppdatera data i databasen. Med Linq to SQL och Linq to Entities, vilka kom i .NET 3.5 respektive .NET 3.5 SP 1 så har det blivit mycket enklare.

Med dessa så används ett nytt interface kallat IQueryable<T>. Detta implementerar IEnumerable, vilket vi har kunnat använda sedan länge. Skillnaden mellan IQueryable och IEnumerable är hur de behandlar datan. Anledningen till att Linq använder sig utav IQueryable är möjligheten till att använda en nyhet kallad Expression Trees, vilket jag kommer att gå igenom här.

För att göra det enklare att studera Expression Trees så kan jag rekommendera att du laddar ned en visualizer som följer med i CSharp Samples for Visual Studio 2008. Du kan ladda ned det här:

http://code.msdn.microsoft.com/csharpsamples

När exemplen är nedladdade och uppackade så öppnar projektet som finns under LinqSamples\ExpressionTreeVisualizer. Kompilera sedan ExpressionTreeVisualizer i release-läget och kopiera ExpressionTreeVisualizer.dll till Mina Dokument\Visual Studio 2008\Visualizers. När detta är gjort så skapar vi ett nytt projekt (jag använder ett Console-projekt) och skriver in detta:

Expression<Func<int, bool>> isTenTree = x => x == 10;

När vi nu kör programmet i debug-läge, så får vi möjlighet att se hur vårt Expression Tree ser ut:

1 - Visualizerknapp

När vi klickar på förstoringsglaset:

2 - Visualizer

Genom att sätta vår Func<> som en Expression<Func<>> så har vi gjort om den till ett Expression Tree. Som ni kan se i bilden ovan så är det en trädvy.

När vi skapar en Func<> så blir det en kompilerad funktion som vi kan avända oss utav direkt och köra, men när vi har en Expression<> så blir det en trädvy som måste kompileras.

Hade det här bara varit en Func<> så hade vi kunnat köra det här:

Func<int, bool> isTen = x => x == 10;
Console.WriteLine(isTen(10));

Och fått resultatet ”True” utskrivet på skärmen.

Kör vi istället med en Expression som inte är kompilerad så måste vi kompilera den för att kunna köra den. Vi kan alltså inte ta ett träd och och ställa frågor mot det direkt.

Expression<Func<int, bool>> isTenTree = x => x == 10;
Console.WriteLine(isTenTree.Compile()(10));

Det här ger samma resultat som koden innan. Genom att köra Compile()-metoden så kompileras vårt träd och vi kan köra det.

Varför ska man använda Expression Trees?

När man skall hämta data som inte finns i minnet på den aktuella servern så är det möjligt att vi måste skriva om koden. Det kan till exempel vara SQL som jag nämnde innan, men även till exempel Web Services (SOAP), SharePoint (CAML), JSON eller något annat. Genom att använda Expression Trees som inte är kompilerade så kan vi på ett enklare sätt läsa ut hur anropet skall se ut än om vi hade använt en kompilerad motsvarighet.

Skillnaden mellan Expression<Func<>> och Func<> är alltså att det förstnämnda är en datastruktur medan det andra är kompilerad kod.

Uppbyggnaden av Expression Trees

Expression<T> har fyra properties som vi kan använda oss utav för att kunna tolka hur datan skall hämtas.

Det är:

  • Body
  • Parameters
  • NodeType
  • Type

Om vi kikar på bilden ovan så kan vi se att vi i det exemplet har:

3 - VisualizerProperties

1. Body

Body är av typen ExpressionEqual, vilket beror på att vi har ”==” för jämförelsen. Hade vi istället haft ”>” så hade det varit GreaterThan.

2. Parameters

Vi skickar med en parameter kallad ”x” som är av typen Int32.

3. NodeType

Det vi har använt är en Lambda Expression.

4. Type

Typen är en Func<Int32, Boolean>, vilket stämmer överens med det vi skapade.

Vi kan alltså med den här informationen se att det vi ska ha är data där den inskickade parametern x har ett värde som är lika med 10, och om så är fallet så returnerar vi true.

Vi kan även använda mer avancerade exempel med fler parametrar.

Expression<Func<int, int, int>> isTenTree2 = (x, y) => x + y;
Console.WriteLine(isTenTree2.Compile()(10, 20));

I det här exemplet så tar vi emot två parametrar av typen Int32 och returnerar sedan summan av dem som en Int32.

Vårt Expression Tree ser nu ut på detta sätt:

4 - TwoParameters

Om vi ska tolka detta så kan vi se att:

1. Body

Body är av typen ExpressionAdd då vi summerar värdena i det.

2. Parameters

Vi har två parametrar, båda av typen Int32.

3. NodeType

NodeType är en Lambda Expression.

4. Type

Typen är en Func<Int32, Int32, Int32>, då vi skickar in två Int32 och sedan returnerar en tredje.

Skapa expressions programmatiskt

Istället för att direkt ange hur vår expression skall vara uppbyggd så kan vi istället skapa den från grunden själva.

För att skapa den senaste frågan ((x, y) => x + y) så kan vi skriva detta:

ParameterExpression paramX = Expression.Parameter(typeof(Int32), "x");
ParameterExpression paramY = Expression.Parameter(typeof(Int32), "y");
BinaryExpression body = Expression.Add(paramX, paramY);
 
ParameterExpression[] parameters = new ParameterExpression[] { paramX, paramY };
 
Expression<Func<Int32, Int32, Int32>> result = Expression.Lambda<Func<Int32, Int32, Int32>>(body, parameters);

Det vi gör här är att vi först skapar de två parametrarna, x och y. Sedan skapar vi en BinaryExpression med dessa två parametrar. För att de skall skickas in som parametrar i vårt Expression Tree så behöver vi även ange dem när vi till sist skapar själva frågan.

Om vi kompilerar och debuggar så kan vi se att vi har fått exakt samma Expression Tree som tidigare.

Vi kan även läsa av dessa propertys ur det skapade Expression Tree vi har. För enkelhetens skull så har jag skapat en metod för att skriva ut innehållet:

static void WriteExpression(Expression<Func<Int32, Int32, Int32>> exp)
{
    Console.Clear();
 
    Console.WriteLine("Body\n-----");
    Console.WriteLine("Body.NodeType: {0}", exp.Body.NodeType.ToString());
    Console.WriteLine("Body.NodeType: {0}", exp.Body.Type.ToString());
 
    Console.WriteLine();
 
    Console.WriteLine("NodeType\n-----");
    Console.WriteLine("NodeType: {0}", exp.NodeType.ToString());
 
    Console.WriteLine();
 
    Console.WriteLine("Parameters\n-----");
    foreach (ParameterExpression p in exp.Parameters)
    {
        Console.WriteLine("{0}", p.Name);
        Console.WriteLine("\tNodeType: {0}", p.NodeType.ToString());
        Console.WriteLine("\tType: {0}", p.Type.ToString());
    }
 
    Console.WriteLine();
 
    Console.WriteLine("Type\n-----");
    Console.WriteLine("Type: {0}", exp.Type.ToString());
}

Vi tar här emot en Expression<Func<Int32, Int32, Int32>> och skriver ut innehållet ur den.

När vi sedan anropar metoden med ”result” som parameter så får vi:

5 - ExpressionTreeProperties

Här har vi läst av alla parametrar och kan se att det är just det vi nyss skapade.

För att kunna exekvera vårt Expression Tree så måste det kompileras innan det kan köras. Då det är en Expression<Func<Int32, Int32, Int32>> när det är okompilerat så blir det en Func<Int32, Int32, Int32> när det är kompilerat.

Vi kan till exempel köra det så här:

Func<Int32, Int32, Int32> summera = result.Compile();
Console.WriteLine(summera(1, 2));

Resultatet blir här ”3”.

De två parametrarna vi har skickat in (1 och 2) är vad som är Left och Right i ett Expression Tree.

Om vi kör den här koden så får vi lätt fram den vänstra och högra sidan av ett Expression Tree:

BinaryExpression exbody = (BinaryExpression)result.Body;
ParameterExpression exleft = (ParameterExpression)body.Left;
ParameterExpression exright = (ParameterExpression)body.Right;
 
Console.WriteLine("Left: {0}", exleft.Name);
Console.WriteLine("Right: {0}", exright.Name);

Det här skrivet ut ”Left: x” och ”Right: y”, då det är de två parametrarna som används. Vi kan även få ut detta i Expression Tree Viewer på ett snyggt sätt:

6 - LeftRightParameters

Vi har alltså full insyn på hur vårt Expression Tree är uppbyggt.

Expression Trees != Lambda Expressions

Jag har visat upp lite exempel på Expression Trees och hur de används med Lambda Expressions, men de är inte samma sak. Vi kan använda Lambda Expressions för att skapa Expression Trees, eller så kan vi skapa dem manuellt som i det föregående exemplet. Alla Lambda Expressions kan dock ej förvandlas till Expression Trees.

Ett exempel på det är:

Action<string> PrintParameter = s => { Console.WriteLine("Parameter: {0}", s); };
Expression<Action<string>> PrintParameterTree = s => { Console.WriteLine("Parameter: {0}", s); };

Den förstnämnda kan vi skapa utan problem, men när vi försöker skapa ett Expression Tree av den så får vi det här felmeddelandet:

A lambda expression with a statement body cannot be converted to an expression tree

Det är alltså två olika funktioner, men som i vissa fall kan användas tillsammans.

Linq to Entities

I det här exemplet så har jag skapat upp en enkel databas med en tabell (Customers) som har tre fält (id, FirstName, LastName). Jag har sedan skapat upp en Entity-modell med den här tabellen.

Jag använder den här koden för att hämta ut alla personer där efternamnet slutar med ”son”:

Database1Entities ent = new Database1Entities();
var customers = ent.Customers.Where(c => c.LastName.EndsWith("son"));
 
foreach (var customer in customers)
{
    Console.WriteLine(customer.FirstName);
}

När vi nu debuggar så får vi fram:

7 - EntitiesDebug

Då Linq to Entities är uppbyggt med Expression Trees så kan vi få fram hur det är uppbyggt när frågan ställs. Klicka på förstoringsglaset för att få fram visualizern:

8 - EntitiesDebugVisualizer

Vi kan se här att det precis som tidigare är ett vanligt Expression Tree, vilket vi skulle kunna bygga dynamiskt om vi vill. Vi behöver alltså inte använda Linq-frågor, eller Lambda Expressions, utan vi kan själva generera det här trädet och på så vis ställa egna frågor.

Om vi ser vilken Type som används (längst ned i bilden) så ser vi att det är en IQueryable<Customers>, vilket gör det möjligt att skapa upp detta Expression Tree. Hade vi kört frågan mot vanliga POCO-objekt så hade vi istället använt en IEnumerable<Customers> då frågorna inte behöver skrivas om.

Då det är ett Expression Tree som lätt kan tolkas så gör det att Entity Framework lätt kan använda SQL Server-providern för att skriva om detta till en lämplig SQL-sats. För att gå ett steg längre i felsökningen så kan vi ladda ned en visualizer för det. Ni hittar en här:

http://visualstudiogallery.msdn.microsoft.com/en-us/99468ece-689b-481c-868c-19e00e0a4e69

Ladda ned dll-filen och lägg i Visualizers-mappen precis som tidigare och starta om Visual Studio. När vi sedan debuggar koden igen så får vi fram det här:

9 - EntityVisualizer

Och när vi klickar där:

10 - EntityVisualizerView

Här kan vi enkelt se att vårt Expression Tree har översatts till:

SELECT 
[Extent1].[id] AS [id], 
[Extent1].[FirstName] AS [FirstName], 
[Extent1].[LastName] AS [LastName]
FROM [dbo].[Customers] AS [Extent1]
WHERE (RIGHT([Extent1].[LastName], CAST(LEN(N'son') AS int))) = N'son'

Det här är den stora fördelen med Expression Trees, det är alltså möjligt att enkelt skriva om dessa till något som kan tolkas av systemet det kopplas mot.

Nyhet i .NET 4.0 – Skapa dynamiska metoder

En nyhet som kommer i .NET 4.0 är att vi på ett smidigt (allt är relativt..) sätt kan skapa dynamiska metoder. Tidigare så har vi varit tvungna att använda System.Reflection.Emit för att göra detta, vilket gör att vi hamnar i ”OpCode hell”. Med .NET 4.0 så går det att lösa mycket smidigare.

För att köra det här så behöver du Visual Studio 2010 och .NET 4.0.

Till skillnad från .NET 3.5 där vi har Expression Trees och därmed kan skapa Expressions med kod, så har vi i .NET 4.0 även stöd för Statement Trees. Med dessa kan vi dynamiskt skapa metoder utan att behöva blanda in IL.

Metoden vi kommer att skapa nu motsvarar det här:

static int DynamicMethod(int parameter)
{
    int value = 0;
 
    do
    {
        if (value < 10)
            value += parameter;
        else
            break;
 
    } while (true);
 
    return value;
}

Vi kommer att skicka in en parameter med värdet ”3” till den här metoden. Sedan körs en loop till dess att if-satsen inte är giltig längre. Om vi kör den här koden kommer resultatet att bli ”12”.

Motsvarande med Statement Expressions är detta:

// Skapa en ParameterExpression som tillhandahåller det inskickade värdet
ParameterExpression parameter = Expression.Parameter(typeof(int), "parameter");
 
// Det lokala värdet som finns i bodyn i vår metod
ParameterExpression localvalue = Expression.Parameter(typeof(int), "localvalue");
 
// En label för att kunna gå ut ur loopen
LabelTarget target = Expression.Label(typeof(int));
 
// Den dynamiska metodens body
BlockExpression block = Expression.Block(
 
    // Skapar en lokal variabel i scopet
    new[] { localvalue },
 
    // Sätter värdet på localvalue till 0
    Expression.Assign(localvalue, Expression.Constant(0)),
 
    Expression.Loop(
        Expression.IfThenElse(
    // Testar om value < 10
            Expression.LessThan(localvalue, Expression.Constant(10, typeof(Int32))),
 
            // Om sant så lägg till det inskickade värdet
            Expression.AddAssign(localvalue, parameter),
 
            // Om falskt så gå ut ur loopen
            Expression.Break(target, localvalue)
        ),
    // Gå ut ur loopen genom att gå till labeln
        target
    )
);
 
// Spara ned ett expression tree
Expression<Func<int, int>> dynamicmethod = Expression.Lambda<Func<int, int>>(block, parameter);

Parametern som skapas i början är den som skickas in i metoden. Sedan skapar vi en lokal variabel som är den som vi i det tidigare exemplet kallar för ”value”. Efter det skapar vi en label, vilket motsvarar break;. Vi får använda denna label för att berätta att vi ska ut ur loopen när villkoret inte är giltigt längre, annars får vi en evighetsloop.

Efter det kommer något som är nytt i C# 4.0, nämligen en BlockExpression, vilket gör det möjligt att skapa kodblock med egen logik i. I det här exemplet så har vi i den själva loopen med en if-sats i.

Vi kan alltså med hjälp av Statement Expressions generera kod i C# som vi sedan kan kompilera med dynamicmethod.Compile() och efter det anropa den precis som tidigare.

Sammanfattning

Expressions är otroligt kraftfulla verktyg för att kunna generera kod, antingen för att kunna kompilera till något annat språk som SQL, eller till C# eller ett dynamiskt språk (DLR använder Statement Expressions väldigt mycket för att generera kod).

I C# 5.0 är det även sagt att det ska bli mer Compiler as a Service, så vi lär definitivt se mer av detta framöver.

Inte alls långt efter att ASP.NET MVC släpptes som RTW så kom preview 1 av ASP.NET MVC 2. Precis som olika versioner av .NET så kommer dock inte kunskapen från tidigare versionen att vara bortkastad, utan tvärtom, du kommer att ha stor nytta av det. Om du inte har arbetat med ASP.NET MVC tidigare så rekommenderar jag att du börjar med min introduktionsartikel som du hittar här:

http://weblogs.asp.net/mikaelsoderstrom/archive/2009/04/02/kom-ig-229-ng-med-asp-net-mvc.aspx

Det jag kommer att ta upp i den här artikeln är starkt typade helpers-funktioner och templates. Om du tidigare har använt Dynamic Data (kom med .NET 3.5 SP 1) så kommer du säkert att känna templates-exemplen som jag kommer att gå igenom. Om du inte har kikat på Dynamic Data så kan jag rekommendera att du skapar ett testprojekt där och labbar lite med t.ex. UIHint() för att få bättre förståelse för vad det kan göra.

Värt att tänka på är att artikeln tar upp preview 1 av ASP.NET MVC 2, och för er som var med i de första förhandsvisningarna av ASP.NET MVC 1 vet säkert att det var mycket som förändrades, så när senare previews av ASP.NET MVC 2 släpps så kan mycket i artikeln vara förändrat. Men nog om det, dags att programmera! :-)

Det första som behövs är ASP.NET MVC 2 Preview 1, det hittar ni här:

http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=d34f9eaa-fcbe-4e20-b2fd-a9a03de7d6dd#tm

När det är installerat så börjar vi med att skapa ett projekt.

1

Lägg märke till att det är .NET 3.5 som används här. Den slutgiltiga versionen kommer att finnas inbyggt i .NET 4.0, men för .NET 3.5 så kommer vi att behöva installera det separat. Om vi kör .NET 3.5 så får vi ingen möjlighet till att köra alla nya funktioner som kommer med .NET 4.0, så vill vi få ut det mesta så rekommenderar jag att du kör med den senare.

När vi nu har vårt projekt så ska vi fortsätta med att skapa en controller som skall användas i exemplet. Projektet har jag givit namnet ”Customers” då det kommer att lista olika kunder. Vi skapar därför en controller vid namn ”CustomersController”. Vi kommer att ha tre olika vyer kopplade till denna, och skapar därmed ActionResults för List, Details och Edit.

3

public ActionResult List()
{
    return View(GetCustomers());
}
 
//
// GET: /Customers/Details/5
 
public ActionResult Details(int id)
{
    Customer customer = GetCustomers().Where(c => c.CustomerId == id).SingleOrDefault();
 
    return View(customer);
}
 
//
// GET: /Customers/Edit/5
 
public ActionResult Edit(int id)
{
    Customer customer = GetCustomers().Where(c => c.CustomerId == id).SingleOrDefault();
 
    return View(customer);
}

För att slippa hämta data från en databas nu så kör vi en liten fuling och lägger till den här metoden i samma klass:

public List<Customer> GetCustomers()
{
    List<Customer> customers = new List<Customer>();
 
    customers.Add(new Customer()
    {
        CustomerId = 1,
        FirstName = "Sven",
        LastName = "Svensson",
        Birthday = new DateTime(1900, 01, 01)
    });
 
    customers.Add(new Customer()
    {
        CustomerId = 2,
        FirstName = "Nils",
        LastName = "Nilsson",
        Birthday = new DateTime(1948, 10, 22)
    });
 
    customers.Add(new Customer()
    {
        CustomerId = 3,
        FirstName = "Petter",
        LastName = "Pettersson",
        Birthday = new DateTime(1940, 05, 12)
    });
 
    return customers;
}

För att det skall fungera så måste vi även skapa en klass i Models-mappen vid namn Customer.cs och som är modellen vi kommer att använda oss utav. Det är en enkel klass med olika properties för CustomerId, FirstName, LastName och Birthday.

public class Customer
{
    public int CustomerId { get; set; }
 
    public string FirstName { get; set; }
 
    public string LastName { get; set; }
 
    public DateTime Birthday { get; set; }
}

Nästa steg är att skapa vyerna för kunderna. Vi skapar en mapp i Views-mappen vid namn Customers. Där skapar vi tre vyer som använder Customer-klassen. Det bör se ut i stil med det här:

2

För att se listningen i menyn så lägger vi slutligen till det här i master-sidan:

<li><%= Html.ActionLink("Customers", "List", "Customers")%></li>

Om vi kör sidan nu så kan vi se att vi kan se sidor för listning, detaljer och ändringar.

Som det ser ut nu så är det inget som skiljer sig från ASP.NET MVC 1, så det vi gör nu är att öppna upp Details.aspx. Här kommer vi att använda oss utav de hårt typade metoderna LabelFor() och EditorFor(). Dessa gör det möjligt att enkelt använda oss utav templates och data annotations för att modifiera utseendet.

Istället för en label för fältnamnet så kommer vi att använda Html.LabelFor():

<%= Html.LabelFor(c => c.CustomerId) %>

Och för textrutorna så kommer vi att använda Html.EditorFor():

<%= Html.EditorFor(c => c.CustomerId) %>

Ser vi på sidan nu så ser vi ingen skillnad. Det vi vill göra är att istället för CustomerId skriva ut Kund-ID på alla ställen där vi använder LabelFor() för det fältet.

Det vi gör nu är att vi öppnar upp Customer.cs i Models-mappen. Här ska vi använda DisplayName-attributet för att ändra vilken text som visas.

[DisplayName("Kund-ID")]
public int CustomerId { get; set; }

Det som händer nu är att vi istället för ”CustomerId” faktiskt får upp ”Kund-ID” på sidan! Det här gör att vi på ett väldigt enkelt sätt kan anpassa utseendet för de olika fälten i modellen. Värt att notera är dock att vi inte kan använda lokalisering i det här attributet, utan då få vi istället lösa det på ett annat sätt.

Nästa funktion som vi ska använda är något annat som användes flitigt med Dynamic Data, nämligen UIHint-attributet. Skillnaden från DisplayName är att det kan användas för att skapa anpassade kontroller för olika fält. Flera fält kan använda sig utav samma UIHint-kontroll.

Som exempel så kommer jag att skapa en MVC View User Control vid namn ”CapitalString.ascx”. Det kontrollen kommer att göra är att köra .ToUpper() på alla strängar som använder denna. Vi lägger denna i en ny mapp under Shared som vi kallar för DisplayTemplates (går även att skapa EditTemplates om man vill göra om fälten som används i Edit-läget).

Utseendet för denna ser ut som följande:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<string>" %>
<%= Model.ToUpper() %>

Kontrollen förväntar sig här att det som tas emot är av typen string, vilket vi anger i Inherits-attributet. Model i det här fallet är det värdet (eller den mer avancerade typen om vi har angett en sådan).

För att applicera denna på ett fält (i det här fallet FirstName”) så lägger vi till UIHint-attributet med namnet på kontrollen som parameter på FirstName-propertyn:

[UIHint("CapitalString")]
public string FirstName { get; set; }

Om vi nu går till en Details-sida så kommer vi se att det står till exempel SVEN istället för Sven.

Vi kan även skapa templates som gäller för alla instanser av den aktuella typen. Det vi ska göra är att byta ut alla DateTime-fält mot antalet år sedan det aktuella datumet så kan vi skapa en kontroll med namnet DateTime.ascx och lägga i DisplayTemplates-mappen. Då DateTime är en value type och vi bara kan skicka med en reference type till ViewUserControl<T> så måste vi använda den ickegeneriska varianten och sedan typa om modellen till en DateTime.

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%= DateTime.Now.AddYears(-((DateTime)Model).Year).Year%>

Det är inte ett helt klockrent sätt att räkna år på, men får duga för det aktuella syftet.

Om vi nu går till en Details-sida så ser vi istället för datumet antal år som har gått sedan det.

Det går med dessa funktioner att skapa delar av sidan som enkelt kan appliceras på ett flertal (eller alla som i DateTime-exemplet) ställen på sidan genom att helt enkelt ändra i en enda fil.

Det här kan vara väldigt användbart när man skall hantera mycket data på sidan och behöver lista samma sak på flera ställen. På det här viset så slipper man ändra på alla olika ställen, utan anger bara med UIHint vilken template som skall användas.

I have just uploaded the first version of my latest project, Twidger, a Twitter widget for Windows Mobile 6.5.

You can use it to read your friends tweets, see your history, search for tweets, see popular searches, read direct messages etc.

I have some screenshots here:

Screen01

When you open the widget you must login to Twitter. No credentials are saved in the Widget.

Screen02

The startscreen with you latest tweet on the top and your friends latest below.

Screen03

If you click on the icon at the top left you will be able to write a new tweet.

Screen04

If you click on “Retweet” you can easy retweet a message.

Screen05

It´s also easy to reply on a tweet, just click reply.

Screen06

If you click on the right soft key (menu) you get this. You can show your friends latest tweets, your direct messages, an archive with your latest tweets and search for tweets.

Screen07

This is the archive.

Screen08

On the search page you will get a list on the most popular searches for the moment.

Screen09

If we click on “Diane Sawyer” in the list, you will automatically get the latest tweets for that search.

Screen10

If we click on “@bkearney” in the list, we will search for that user. The same works with hashtags like #WinMo.

All external links will be opened in your standard browser.

Future features:

  • Better design.
  • Post direct messages.
  • Better design.
  • Read all latest tweets (not just from friends).
  • Better design.
  • Integrate with shorten URL services.
  • Ohh… Did I mention better design? If not: Better design.

Have I missed any features? Please let me know!

Download Twidger!

More Posts