Thursday, April 15, 2010

Binding x Top Records using LINQ

Hi,

In this post I'm going to show you how to implement a data bind of the top x objects of a database, based on their rating, using LINQ. In this particular context, we want to show the top 3 songs of the database.

Let's consider our 3 tables:

USER:



SONG:


VOTE:



Pre-Condition: Ajax
Star Rating Logic implemented (if you want me to talk about this on another post, just email us, and I'll be glad to help out)
- - -

Ok, we're going to use an ASP.NET Repeater to bind the data:

<h2 class="title">Top 3 Songs:<asp:AjaxScriptManager ID="AjaxScriptManager1" runat="server">

</asp:AjaxScriptManager>

</h2><br />

<asp:Label ID="notificationLabel" runat="server" CssClass="titleField">asp:Label><br /><br /><br />

<asp:Repeater ID="Repeater1" runat="server" DataSourceID="songDataSource">

<ItemTemplate>

<div class="titleMain">

<asp:ImageButton OnCommand="redirectToFriend_Click" CommandArgument='<%# Eval("ownerID") %>' ID="ownerPhoto" class="left" runat="server" ImageAlign="Middle" Height="40px" Width="40px" ImageUrl='<%# DataBinder.Eval (Container, "DataItem.photo", "~/photoUploads/{0}") %>' /><br />

<asp:Label ID="firstNameLabel" runat="server" Text='<%# Bind("firstName") %>' CssClass="titleField" /> <br /><br />

"<asp:Label ID="songNameLabel" runat="server" Text='<%# Bind("plainName") %>' CssClass="titleField" />"<br />

<asp:Label ID="Label2" runat="server" Text="Average Rating:" CssClass="titleField" />

<asp:Label ID="ratingLabel" runat="server" Text='<%# Bind("VotesAvg", "{0:#.##}") %>' CssClass="titleField" /><br />

<br />

<asp:MediaPlayer ID="MediaPlayer1" runat="server" Height="40px" Width="320px"

MediaSkinSource="~/AudioGray.xaml" MediaSource='<%# DataBinder.Eval (Container, "DataItem.songName", "~/mp3Uploads/{0}") %>' Volume="1">

</asp:MediaPlayer>

<br /><br /><br /><br />div>

ItemTemplate>

asp:Repeater>

<asp:LinqDataSource ID="songDataSource" runat="server"

ContextTypeName="Ogaite.OgaiteLINQtoSQLDataContext"

Select="new (songID, VotesAvg, plainName, songName, firstName, ownerID, photo)" TableName="Votes"

onselecting="songDataSource_Selecting1" >

asp:LinqDataSource>

Relevant details here, which go within the context of this post, are the following labels: "firstNameLabel" , "songNameLabel" and "ratingLabel" (most important). These are going to give us Who are the 3 amazing composers, which songs made it to the top, and what is their current average rating.

Take a look at the LinqDataSource, and the correspondent Select Statement. We have column names that don't belong to the table "Votes". We even have "VotesAvg" which doesn't belong anywhere..... yet. The FULCRAL part here is the "onselecting="songDataSource_Selecting1".

Here is the correspondent code behind:

protected void songDataSource_Selecting1(object sender, LinqDataSourceSelectEventArgs e)

{

TuganologyLINQtoSQLDataContext dbData = new TuganologyLINQtoSQLDataContext();

var ratings = (from v in dbData.Votes

group v by new { v.songID, v.Song.plainName, v.Song.songName, v.Song.User.firstName, v.Song.User.userID, v.Song.User.photo } into vgroup

select new

{

VotesAvg = vgroup.Average(avg => (double)avg.vote1),

plainName = vgroup.Key.plainName,

SongID = vgroup.Key.songID,

songName = vgroup.Key.songName,

firstName = vgroup.Key.firstName,

photo = vgroup.Key.photo,

ownerID = vgroup.Key.userID

});

var grouped = (from g in ratings

orderby g.VotesAvg descending

select g);

var top = (from t in grouped.Take(3)

orderby t.VotesAvg descending

select t);

e.Result = top;

}

=) Explaining:

In "var ratings" we're essentially getting the songID of the song; its name; songName (this is a guid, which I use to get the path in mediaplayer); firstName (first name of the composer); userID of the composer; and photo of the composer. We're using LINQ "group by", and grouping these columns into "vgroup" (it can be ANY name you want). Notice how we can use "v.Song.User.userID" ! This is very very handy, and we don't need to do any joins, as our Table "Song" has "userID" (foreign key), and by having this, we automatically have access to all columns within "User" Table. The rest follows the same logic. But what does "vgroup" contain?

This will give what "vgroup" will be comprised of:

select new

{

VotesAvg = vgroup.Average(avg => (double)avg.vote1),

plainName = vgroup.Key.plainName,

SongID = vgroup.Key.songID,

songName = vgroup.Key.songName,

firstName = vgroup.Key.firstName,

photo = vgroup.Key.photo,

ownerID = vgroup.Key.userID

});

We're defining "VotesAvg" to be the average of all the instances of "vote1" (LAMBDA Expression here). The rest is simple. Now go back and check our Repeater Select Statement. It makes more sense now right?

Ok, after this, we're declaring "var grouped" to order "ratings" by "VotesAvg" (descending). And then declaring "var top" to take the 3 top songs.

We have implemented all the logic.

Example Output:


Questions?

Thanks for reading!


TR


Tuesday, March 9, 2010

IIS 7.5 and 7.0, Certificate Trust Lists and SSL Bindings

IIS 7 comes with Windows Server 2008, the following procedure doesn't work for IIS 7.5 from Windows Server 2008 R2 (requires a hotfix from MS, not public at the time of writing this post).

What we want, is to create a Certificate Trust List and bind it to a IIS Site, on IIS 6.0 it's pretty much straight forward. On IIS 7 it's not that simple.

Before anything else, we need the MakeCTL.exe tool, wich can be obtained from Windows SDK for Windows Server 2008 and .NET Framework 3.5 package, found here: http://www.microsoft.com/downloads/details.aspx?FamilyId=E6E1C3DF-A74F-4207-8586-711EBE331CDC&displaylang=en

On the application server or the machine where the application resides, we need to run the tool with elevated privileges (Run as Administrator).

At the first screen click Next.

The following screen will ask for a prefix that identifies the CTL (later we will use this prefix as the Friendly Name for our CTL), for our example we will call it TuganologyTest.

Then we need to Add a Purpose for our CTL, in this case we'll use an Object ID with the value 1.3.6.1.4.1.311.10.1, wich identifies a "Certificate Trust List" purpose, a full list can be found here: http://support.microsoft.com/kb/287547

After we add a purpose, it should look something like this:

Choose Next.

Now we need to choose Add from Store option to select the certificates witch our CTL is going to contain (I chose a well known issuer for this example, you should use whatever issuer you need to trust in your application).

On this screen, we may use the View Certificate option to check if everything is OK.

At the Certificate Trust List Storage, we need to select the Certificate Store and click browse, check the Show physical stores option and navigate to Local Computer under Intermediate Certification Authorities.



After clicking OK and Next, we need to validate the Friendly Name of the CTL (witch we chose at the begining). Select Next and then Finish.

Now we have our CTL created, to make sure everything is accordingly with what we want, we can open an MMC Console, add the Certificates Snap-In (Computer Account), browse to the Intermediate Certification Authority and check if our CTL appears there.

Now that we wrapped up the CTL issue, we need to associate it to our application, for this we will use the netsh command, more information on it can be found here: http://technet.microsoft.com/en-us/library/cc725882(WS.10).aspx

First of, we need to run a command console with elevated privileges (run cmd.exe as administrator).

Execute the command: netsh http show sslcert

The output should be something like this:

SSL Certificate bindings:
-------------------------

IP:port : 0.0.0.0:443
Certificate Hash : 24533c00fee7af7796331ca00f7ca29c49218a80
Application ID : {4dc3e181-e14b-4a21-b022-59fc669b0914}
Certificate Store Name : MY
Verify Client Certificate Revocation : Enabled
Verify Revocation Using Cached Client Certificate Only : Disabled
Usage Check : Enabled
Revocation Freshness Time : 0
URL Retrieval Timeout : 0
Ctl Identifier : (null)
Ctl Store Name : (null)

DS Mapper Usage : Disabled
Negotiate Client Certificate : Disabled

In our case, I have an application with SSL listening on port 443 (I created a Self Signed Certificate to achieve this)

Note that both CTL Identifier and CTL Store Name (red lines) are null, for future reference we need to keep the information from the IP:port, Certificate Hash and Application ID fields.

Now we need to delete this binding, witch can be done by running the following command:

netsh http delete sslcert ipport=0.0.0.0:443

After we delete it, we need to recreate it, but this time we will add our previously created CTL to the pot with the following command:

netsh http add sslcert ipport=0.0.0.0:443 certhash=24533c00fee7af7796331ca00f7ca29c49218a80 appid={4dc3e181-e14b-4a21-b022-59fc669b0914} sslctlidentifier=TuganologyTest sslctlstorename=CA

Please note that we used the Friendly Name of the CTL, the certhash and appid are the same from the previous binding (witch we deleted earlier).

Now if we run the command to check the bindings again:

netsh http show sslcert

We get the following output:

SSL Certificate bindings:
-------------------------

IP:port : 0.0.0.0:443
Certificate Hash : 24533c00fee7af7796331ca00f7ca29c49218a80
Application ID : {4dc3e181-e14b-4a21-b022-59fc669b0914}
Certificate Store Name : (null)
Verify Client Certificate Revocation : Enabled
Verify Revocation Using Cached Client Certificate Only : Disabled
Usage Check : Enabled
Revocation Freshness Time : 0
URL Retrieval Timeout : 0
Ctl Identifier : TuganologyTest
Ctl Store Name : CA

DS Mapper Usage : Disabled
Negotiate Client Certificate : Disabled

Now the CTL Identifier and Store name are no longer with null values, in fact we now have our application associated with our CTL.

I've written this post in English because I didn't find much information about this procedure (CTLs and IIS7.x bindings) online, hope it helps.

For doubts please contact: tuganologia@gmail.com

NR

Tuesday, March 2, 2010

ASP.NET Membership / Custom DB Part 2

Tentando seguir uma corrente coerente, vamos pegar no último artigo de ASP.NET e dar um uso prático ao que foi exposto.

Pré-Requisito: Web Application criada com a DB correspondente configurada; Login e CreateUserWizard Controls adicionados. A DB chama-se "Tuganologia".

Vamos usar LINQtoSQL, portanto vamos começar por criar as suas respectivas classes:

- Add new Item --> LINQtoSQL Classes , nome “TuganologiaLINQtoSQL.dbml” --> Add

De seguida ir ao Database Explorar e arrastar todas as tables (custom e aspnet membership) para o .dbml ; com isto é criado o Data Context do TuganologiaLINQtoSQL.


O que se pretende com este post é fazer a bridge entre as tabelas aspnet e as custom. Na nossa DB “Tuganologia”, criei uma tabela denominada “User”, e é precisamente nessa tabela que vai ocorrer a bridge.
Qual o control onde tal deverá acontecer? No CreateUserWizard obviamente.

Temos um CreateUserWizard de ID=”CreateUserWizard1” e o que nos interessa é alterar o evento “CreatedUser” (ocorre imediatamente após ter sido atribuído um GUID para o userID do user recém registado). Vamos então alterar esse evento. Duplo click no “CreatedUser” event e vamos parar ao code behind:

protected void CreateUserWizard1_CreatedUser(object sender, EventArgs e)
{

}


Antes de alterar o evento, há algo a fazer primeiro: Resolvi criar uma classe denominada de “TuganologiaDataLayer.cs” que pode vir a conter todas as funções de modulação da nossa DB. Vamos adicionar a seguinte função:


public class TuganologiaDataLayer
{
TuganologiaLINQtoSQLDataContext database = new TuganologiaLINQtoSQLDataContext();

public void CreateUser(Guid userId, string email)
{
User userDetails = new User();
userDetails.userID = userId;
userDetails.firstName = "";
userDetails.lastName = "";
userDetails.email = email;

database.Users.InsertOnSubmit(userDetails);
database.SubmitChanges();
}
}



Como podem observar criámos uma nova instância do nosso Data Context e na função tratámos o User como um objecto (LINQ traz-nos ORM, Object Relational Mapping, à Framework .NET, o que nos permite manipular a DB como "pure objects"). O resto já vão perceber quando finalmente alterarmos o evento “CreatedUser”:

protected void CreateUserWizard1_CreatedUser(object sender, EventArgs e)
{

MembershipUser newUser = Membership.GetUser(((CreateUserWizard)sender).UserName);

Guid newUserId = (Guid)newUser.ProviderUserKey;

// aqui é que fazemos a bridge!
TuganologiaDataLayer dataLayer = new TuganologiaDataLayer();
dataLayer.CreateUser(newUserId, ((CreateUserWizard)sender).Email);
}

Este evento vai executar o GetUser do controlo “CreateUserWizard” (User que acabou de se registar no nosso site); após isso criamos o nosso Guid “newUserId” , que vai corresponder à Guid atribuída a esse user aquando do seu registo ((Guid)newUser.ProviderUserKey).
Resta-nos instanciar a nossa Data Layer e chamar a nossa função “CreateUser”.

A nossa custom table “User” tem agora o userID (Guid) e o Email inserido no CreateUserWizard. =)


Qualquer dúvida contactar via: tuganologia@gmail.com

TR

Erro - Content Deployment - The remote upload Web Request failed

Este erro quando acompanhado por um evento do windows com o ID 6398, poderá corresponder a uma das seguintes situações, mediante descrição:

Situação 1

Se o content deploy falhar na fase de transporte com o erro "The remote upload Web Request failed" e for acompanhado com a seguinte entrada no eventos de aplicação:

Event ID: 6398Source: Windows Sharepoint ServicesDescription: The Excecute method of job definition Microsoft.Sharepoint.Publishing.Administration.ContentDeploymentJobDefination threw an exception. The remote server returned an error (404) Not Found.

Deve-se ir ao log de IIS da Central Administration da máquina de destino do content deployment e verificar se existe uma entrada semelhante à seguinte:

POST /_admin/Content+Deployment/DeploymentUpload.aspx filename=%22ExportedFiles13.cab%22&remoteJobId=%11456fa7ed-ddcdedcdd-9aae-a1adsf5re1db%22 1976 - xxx.xxx.xxx.xxx - 404 13 0 62

Estes eventos denunciam que a falha origina na limitação do IIS 7 em não suportar o upload de ficheiros com mais de 29Mb.

Tipicamente o MOSS parte os ficheiros cab a 10Mb, mas nem sempre isso acontece. A solução passa por adicionar a seguinte entrada no web.config da central administration na máquina de destino do content deployment:

<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="104857600"/>
</requestFiltering>
</security>
</system.webServer>

104857600 = 100 Mb, este valor revelou-se suficiente, mas pode ser ajustado às necessidades.

Situação 2

Na fase de transporte o content deployment pode falhar com o erro "The remote upload Web Request failed", se existir um evento com o ID 6398 que tenha na descrição esta frase "The remote name could not be resolved: ‘nomedoservidor.teste.local’".

Tem de ser adicionado o FQDN do servidor de destino do content deployment ao ficheiro de HOSTS da máquina responsável pela exportação.
ex:HOSTS da máquina de exportação:
xxx.xxx.xxx.xxx nomedoservidor.teste.local

Não é suficiente apenas o nome da máquina.
NR

Thursday, February 25, 2010

Portais Anónimos - Desactivar vista de formulários

Um portal Sharepoint cujo o acesso esteja anónimo, possibilita a qualquer utilizador aceder às listas através da vista de formulários (View all site content).

Por exemplo, ao completar o url de um portal público/anónimo com a seguinte string:

_layouts/viewlsts.aspx

permite que um utilizador sem privilégios tenha acesso às listas.

Para impossibilitar tal acesso é necessário activar uma feature, com o seguinte comando:

stsadm -o activatefeature -url -filename ViewFormPagesLockDown\feature.xml

Nota: Numa instalação padrão do MOSS, esta feature encontra-se na seguinte localização:

C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\TEMPLATE\FEATURES

Após a activação da feature, é aconselhável efectuar um IISRESET. É também necessário desactivar e voltar a activar o acesso anónimo ao portal, tanto na zona de Authentication Providers do Central Administration, como nas permissões avançadas do Site Collection.

Nota: A feature não aparecerá na zona padrão de features, pois existe uma chave no xml da mesma que a torna escondida, para efeitos de despiste de problemas pode ser uma boa opção torná-la visivel.

NR

Tuesday, February 23, 2010

Inserir tabelas de ASP.NET Membership numa Custom Database

Bem, a funcionalidade Membership de ASP.NET é algo muito útil ao nível de aprovisionamento e gestão de utilizadores. Ao usar as standard features de membership, irão reparar que a DB criada é a ASPNETDB. Tudo muito bem... mas se quisermos usar a nossa CUSTOM database? Vamos ter de fazer deploy de uma base de dados extra apenas para gerir users??! Não.

Eis uma solução simples e eficaz:

Vou criar uma DB dummy chamada "TESTuganologia" e vou criar, vá lá, uma tabela "Users" (com userID e Name)... não interessa, é apenas uma tabela dummy para fins de demonstração. A nossa DB neste momento consta apenas da tabela recém criada. Vamos inserir as default tables de ASP.NET?

Com permissões de Administrator, aceder a uma nova janela DOS e aceder ao seguinte path:

c:\windows\Microsoft.NET\Framework\v2.0.50727

Correr o seguinte comando: aspnet_regsql.exe -E -S localhost -d TESTuganologia -A all -sqlexportonly c:\membership.sql

Com este comando, irá ser criado o novo ficheiro c:\membership.sql

Abrir membership.sql e seleccionar "EXECUTE". Isto vai inserir todas as tabelas, triggers e stored procedures de ASP.NET Membership, à nossa custom db.

TR

Monday, February 22, 2010

Authentication failed because the remote party has closed the transport stream

Num ambiente de farm, ao aceder às Search Settings de uma SSP pode ocorrer o seguinte erro:

Authentication failed because the remote party has closed the transport stream.

Esta situação ocorre quando tentamos aceder à página de definições de pesquisa num servidor da farm que não seja o de indexação.
O constrangimento ocorre porque ao aceder a esta página o servidor tenta comunicar com o servidor de indexação através do webservice "SearchAdmin.asmx", esta comunicação é efectuada por SSL.

Este comportamento denuncia problemas no certificado atribuido ao site "Office SharePoint Server Web Services" automáticamente pelo sharepoint aquando a instalação do mesmo.

Existem pelo menos dois workarounds:

  1. Aceder à página de Search Settings directamente no servidor de indexação.
  2. Gerar um novo certificado para o site de webservices, tal poderá ser feito recorrendo à aplicação selfssl que consta no Resource Kit do IIS 6.0. Após a instalação do resource kit, pode-se executar o seguinte comando:

selfssl.exe /N:CN= /K:1024 /V: /S:xxxxxxxxxx /P:xxxxx

Parametros em detalhe:

/K Codificação
/S ID do site
/P Porto do site

NR