Tuesday, October 12, 2010

Fluent Search Interface with some Func




UPDATED: 10/18/2010 11:42PM

So…how do you start a fluent interface? By typing how you’d want your code to read, at least that’s how I do it. So I created a unit test project and started typing how I’d like my interface to work. This is what I came up with:

Search<Person>.With(myPersonList).For("deran").By("FirstName").Result;

Alright well, that was simple enough. Now how do I make it work? First I’ll create my Search class with the With method:

    public class Search<T>
{
internal Search(IEnumerable<T> value)
{ }

public static ISearchRequired<T> With(IEnumerable<T> source)
{
return new Search<T>(source);
}
}

You can see here that I’m returning a ISearchRequired<T>, which looks like this:

    public interface ISearchRequired<T>
{
ISearchExtensions<T> For(string value);
ISearchExtensions<T> Not(string value);
}

The reason I have this interface is so I can control what the developer using my fluent interface sees. Each of these items returns the ISearchExtensions, which has more options. First I want to show the finished Search<T> class:

    public class Search<T> : SearchExtensions<T>, ISearchRequired<T>
{
internal Search(IEnumerable<T> value)
:
base(value)
{ }

public static ISearchRequired<T> With(IEnumerable<T> source)
{
return new Search<T>(source);
}

public ISearchExtensions<T> Not(string value)
{
_searchCriteria.Add(
new SearchCriteria { Value = value, NotEqual = true });
return this;
}

public ISearchExtensions<T> For(string value)
{
_searchCriteria.Add(
new SearchCriteria { Value = value });
return this;
}
}

Here’s the ISearchExtensions<T> interface:

    public interface ISearchExtensions<T>
{
ISearchExtensions<T> By(Expression<Func<T, string>> valueExpr);//changed from (string value)
ISearchExtensions<T> AndFor(string value);
ISearchExtensions<T> OrFor(string value);
ISearchExtensions<T> AndNot(string value);
IEnumerable<T> Result { get; }
}

You can also see in my completed Search<T> class, I have a SearchCriteria object that looks like this:

    public class SearchCriteria
{
public Expression<Func<T, string>>/*changed from string*/ By { get; set; }
public string Value { get; set; }
public LogicalOperator LogicalOperator { get; set; }
public bool NotEqual { get; set; }
}

public enum LogicalOperator
{
None, And, Or
}

Simple stuff so far…now for the fun stuff…the actual implementation, which looks like this:

public class SearchExtensions<T> : ISearchExtensions<T>
{
protected IList<SearchCriteria> _searchCriteria;
private readonly IEnumerable<T> _source;
internal SearchExtensions(IEnumerable<T> source)
{
_source = source;
_searchCriteria =
new List<SearchCriteria>();
}

public ISearchExtensions<T> By(Expression<Func<T, string>> valueExpr)
{
//REMOVED: var value = NoMagicStringHelper.GetPropertyFrom(valueExpr);

foreach (var sc in _searchCriteria.Where(x => x.By == null))
sc.By = value;

return this;
}


public ISearchExtensions<T> AndFor(string value)
{
_searchCriteria.Add(
new SearchCriteria { Value = value, LogicalOperator = LogicalOperator.And });
return this;
}

public ISearchExtensions<T> OrFor(string value)
{
_searchCriteria.Add(
new SearchCriteria { Value = value, LogicalOperator = LogicalOperator.Or });
return this;
}

public ISearchExtensions<T> AndNot(string value)
{
_searchCriteria.Add(
new SearchCriteria { Value = value, NotEqual = true });
return this;
}

public IEnumerable<T> Result
{
get
{
var predicate = PredicateBuilder.True<T>();
predicate = _searchCriteria.Where(x => !x.NotEqual)
.Aggregate(predicate, (current, criteria) =>
criteria.LogicalOperator ==
LogicalOperator.Or ?
current.Or(StartsWith(criteria.By, criteria.Value)) :
current.And(StartsWith(criteria.By, criteria.Value)));
predicate = _searchCriteria.Where(x => x.NotEqual)
.Aggregate(predicate, (current, criteria) =>
current.AndNot(StartsWith(criteria.By, criteria.Value)));
return _source.AsQueryable().Where(predicate);

//changed from:
//var predicate = PredicateBuilder.True<T>();
//predicate = _searchCriteria.Where(x => !x.NotEqual)
// .Aggregate(predicate, (current, criteria) =>
// criteria.LogicalOperator == LogicalOperator.Or ?
// current.Or(Contains(criteria.By, criteria.Value)) :
// current.And(Contains(criteria.By, criteria.Value)));
//predicate = _searchCriteria.Where(x => x.NotEqual)
// .Aggregate(predicate, (current, criteria) =>
// current.AndNot(Contains(criteria.By, criteria.Value)));
//return _source.AsQueryable().Where(predicate);
}
}






private static Expression<Func<T, bool>> StartsWith(Expression<Func<T, string>> searchBy, string searchValue)
{
var search = Expression.Constant(searchValue.ToLower(), typeof(string));
var param = Expression.Parameter(typeof(T), "x");
var containsMethod = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
var containsExp = Expression.Call(getProperty(searchBy, param), containsMethod, search);

return Expression.Lambda<Func<T, bool>>(containsExp, param);
}

//changed from:
//private static Expression<Func<T, bool>> Contains(string searchBy, string searchValue)
//{
// var search = Expression.Constant(searchValue, typeof (string));
// var searchByParam = Expression.Parameter(typeof (T), searchBy);
// var searchByExp = Expression.Property(searchByParam, searchBy);
// var methodInfo = typeof (string).GetMethod("Contains", new[] {typeof (string)});
// var containsExpression = Expression.Call(searchByExp, methodInfo, search);
// return Expression.Lambda<Func<T, bool>>(containsExpression, searchByParam);
//}

}

I think most of these are pretty self explanatory, with the exception of Result & Contains. Result takes advantage of a PredicateBuilder that I’ll show in a minute. Basically, it grabs all the search criteria that is equal and checks whether or not the command is or or and and builds the predicate. The next part grabs all the not equal criteria and builds its predicate. Finally it returns the course AsQueryable() where predicate. The AsQueryable is important because it’s what allows us not to have to call Compile() anywhere in our expression building.

Thanks to Rob White commenting below, I also added a NoMagicStringHelper class mentioned above in the new By() method. Here it is:

private static Expression getProperty(Expression<Func<T, string>> searchBy, Expression param)
{
Expression propExp = null;
var first = true;
foreach (var s in searchBy.Body.ToString().Split('.').Skip /*parameter*/(1))
if (first)
{
propExp =
Expression.Property(param, s);
first =
false;
}
else
{
if (s.StartsWith("ToString"))
propExp =
Expression.Call(propExp, "ToString", null);
else
propExp = Expression.Property(propExp, s);
}

return Expression.Call(Expression.Coalesce(propExp, Expression.Constant(String.Empty)), "ToLower", null);
}

//DELETED:
//public class NoMagicStringHelper
//{
// public static string GetPropertyFrom<T>(Expression<Func<T, string>> expr)
// {
// MemberExpression me = null;
// var body = expr.Body;
// if (body is MemberExpression) me = body as MemberExpression;
// else if (body is UnaryExpression)
// {
// var ue = body as UnaryExpression;
// me = ue.Operand as MemberExpression;
// }
// if (me == null) throw new NotImplementedException("Only Member and Unary Expressions implemented.");
// return me.Member.Name;
// }
//}

The Contains method just builds the Contains expression based on the search criteria. I used this same approach back in May, for more details, check it out.

The PredicateBuilder is pretty cool, I found it here on the C# 4.0/3.01 in a Nutshell site. It looks like this:

public static class PredicateBuilder
{
public static Expression<Func<T, bool>> True<T>() { return f => true; }
public static Expression<Func<T, bool>> False<T>() { return f => false; }

public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());

return Expression.Lambda<Func<T, bool>>(Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
}

public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());

return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters);
}

public static Expression<Func<T, bool>> AndNot<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, Expression.Not(invokedExpr)), expr1.Parameters);
}
}

I added the AndNot<T> method to handle the Not and NotFor methods of my interface.

And of course, I have some tests to go along: I added more tests, but this post is long enough. You can download the source and check them out though.

    [TestFixture]
public class SearchTests
{
private List<Person> myPersonList;

[
TestFixtureSetUp]
public void Setup()
{
myPersonList =
new List<Person>();
myPersonList.Add(
new Person() { FirstName = "pappa", LastName = "racer" });
myPersonList.Add(
new Person() { FirstName = "speed", LastName = "racer" });
myPersonList.Add(
new Person() { FirstName = "george", LastName = "jetson" });
}

[
Test]
public void Result_should_return_search_results()
{
var result = Search<Person>.With(myPersonList).For("speed").By(x=>x.FirstName).Result;

Assert.That(result.Count(), Is.EqualTo(1));
}
[
Test]
public void Result_should_return_search_results_not_containing_speed()
{
var result = Search<Person>.With(myPersonList).Not("speed").By(x => x.FirstName).Result;

Assert.That(result.Count(), Is.EqualTo(2));
}
[
Test]
public void Result_should_return_search_results_containing_speed_or_pappa()
{
var result = Search<Person>.With(myPersonList).For("speed").OrFor("pappa").By(x => x.FirstName).Result;

Assert.That(result.Count(), Is.EqualTo(2));
}
[
Test]
public void Result_should_return_search_results_containing_pappa_or_speed_by_firstname_or_jetson_by_lastname()
{
var result = Search<Person>.With(myPersonList).For("pappa").OrFor("speed").By(x => x.FirstName).OrFor("jetson").By(x=>x.LastName).Result;

Assert.That(result.Count(), Is.EqualTo(3));
}
[
Test]
public void Result_should_return_search_results_containing_pappa_or_george_by_firstname_and_not_racer_by_lastname()
{
var result = Search<Person>.With(myPersonList).For("pappa").OrFor("george").By(x => x.FirstName).AndNot("racer").By(x=>x.LastName).Result;

Assert.That(result.Count(), Is.EqualTo(1));
}
}

internal class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}

You can download the source code here.

Thanks for reading and as always, please let me know what you think!

Shout it

kick it on DotNetKicks.com

Thursday, September 30, 2010

Dev Bandwagon – Part 1



So in my quest to find a new project to blog about, my brother, Dane, and I stumbled across one. I don’t know of anything that currently exists, but the idea is to have one place to go and download all the different tools used for specific languages. I know for the past couple years, a LOT of developers have complained that they don’t know what, when, or how to use most the tools because they’re coming out too fast and they don’t have time to learn them.

What Dane and I would like to create to solve the problem is a resource for developers to find all tools and tutorials to help them learn. We’ve started writing up some of the user stories using Agile Zen, which is a terrific tool! Here’s what we’ve come up with so far:

image

We’ll probably start out with only .Net tools and then grow it into some of the other languages. We’re pretty confident that the hardest part will be the categorization of the tools. We’re thinking we’ll have categories and sub-categories, something like this:

image

I think the categorization is what we’ll have to lean on the community for as well as the tools themselves. Please let me know what you think and whether or not you know of anything in existence of if you think this is a good/bad idea.

Thanks for reading!

kick it on DotNetKicks.com

Tuesday, August 31, 2010

Comma-delimited Column with SQL 2005




Long time no blog. The month of August is ridiculously busy at my job, so I haven’t really had a chance to post. Since this month is about over, I should be able to start posting regularly again. Luckily this past month I had the opportunity to learn quite a bit, particularly about fluent NHibernate. Most likely I will be posting about that topic in the near future…mainly to praise nhprof, which is a necessity if transitioning to NHibernate.

Okay, so let’s get started. I seem to always find myself needing a script that will create a comma-delimited column. Well, I found a nice post on the topic, but I didn’t like a hard-coded number he has in his script. So, I think I have a solution to the issue, but I’m curious to see what others think. So…here’s the SQL:

DECLARE @ENDIDENTIFIER CHAR
SET
@ENDIDENTIFIER=';'

SELECT
REPLACE((SELECT many.Items + ','
FROM many WHERE many.foreignKey = one.primaryKey
FOR xml path('')) + @ENDIDENTIFIER, ',' + @ENDIDENTIFIER, '') AS csv
FROM one

Here’s the same script without the @ENDIDENTIFIER:

SELECT
REPLACE((SELECT many.Items + ','
FROM many WHERE many.foreignKey = one.primaryKey
FOR xml path('')) + ';', ',;', '') AS csv
FROM one

The @ENDIDENTIFER is just there so I don’t have to remember where to put my identifier to replace. This way it’s easy if I have a column that might have a ;, I can easily use another identifier.

This seems to perform well, but haven’t used it on anything with more than 50,000 records. If any of you decide to use it, I’d be curious to know if you improve it or if it performs well. Thanks for reading!

kick it on DotNetKicks.com

Friday, July 16, 2010

Authenticating Against Multiple Domains with C# & StructureMap




In my place of work, we had a vendor come in and try to sell us a product that couldn’t authenticate to multiple domains. Well, we have multiple domains. Their suggestion was to consolidate into one…obviously we’re not using their product. So, in an effort to help out, I thought I’d share a method of authenticating to multiple domains.

As always, we start out with our interface…

  public interface IAuthenticationService
{
bool Validate(string username, string password);
string DefaultRedirectUrl { get; }
}

Pretty straightforward so far…I added in a DefaultRedirectUrl in case you want to route to different places depending on which domain you authenticated against.

Okay, let’s implement our interface…

    public class AuthenticationService : IAuthenticationService
{
private readonly IAuthenticationService[] _authenticators;
private string _redirecturl;

public AuthenticationService(IAuthenticationService[] authenticators)
{
_authenticators = authenticators;
}

public bool Validate(string username, string password)
{
var isValid = false;
foreach (var authenticator in _authenticators)
{
if (authenticator.Validate(username, password))
{
isValid =
true;
_redirecturl = authenticator.DefaultRedirectURL;
break;
}
}
return isValid;
}

public string DefaultRedirectUrl
{
get { return _redirecturl; }
}
}

Alrighty…so all this does is takes in an array of authentication services and then loops through them checking if they’re valid and if they are, sets isvalid to true, the redirect url, and returns.

Basically this is our master authentication service. Now, we just need one for each one of our domains. Since they’re both authenticating to AD, we’ll create a base class like this:

    public abstract class ActiveDirectoryAuthenticator : IAuthenticationService
{
private readonly ILDAP _ldap;

protected ActiveDirectoryAuthenticator(ILDAP ldap)
{
_ldap = ldap;
}

public bool Validate(string username, string password)
{
var authenticated = false;
try
{
var entry = new DirectoryEntry(_ldap.URL, username + "@" + _ldap.HostName, password);
object nativeObject = entry.NativeObject;
authenticated =
true;
}
catch (DirectoryServicesCOMException)
{
}
return authenticated;
}

public virtual string DefaultRedirectUrl
{
get { return "/"; }
}
}

So, my ILDAP interface is the same one I blogged about here. The DefaultRedirectUrl here is virtual and meant to be overridden, otherwise it’ll just redirect to the root. Okay so now we need to inherit this authenticator in our domain authentication service classes like this…

Domain #1

public class Domain1AuthenticationService : ActiveDirectoryAuthenticator
{
public Domain1AuthenticationService() : base(DependencyRegistrar.Resolve<ILDAP>(DependencyNames.Domain1LDAP))
{ }

public override string DefaultRedirectUrl
{
get
{
return "/domain1/default.aspx";
}
}
}

Domain #2

public class Domain2AuthenticationService : ActiveDirectoryAuthenticator
{
public Domain2AuthenticationService() : base(DependencyRegistrar.Resolve<ILDAP>(DependencyNames.Domain2LDAP))
{ }

public override string DefaultRedirectUrl
{
get
{
return "/domain2/default.aspx";
}
}
}

Pretty simple right? Basically we’re setting our RedirectUrl and calling our ILDAP from StructureMap. You can read more about how I implement StructureMap here. Now for the good ole StructureMap code that goes in the Registry…

ForRequestedType<ILDAP>()
.AddInstances(x =>
{
x.OfConcreteType<
LDAP>()
.WithName(
DependencyNames.Domain1LDAP)
.SetProperty(y => y.WithBaseDN(ou =>
"Users", dc => "dc1", dc => "domain", dc => "com")
.WithHost(
"dc1.domain.com"));

x.OfConcreteType<
LDAP>()
.WithName(
DependencyNames.Domain2LDAP)
.SetProperty(y => y.WithBaseDN(ou =>
"Users", dc => "dc2", dc => "domain", dc => "com")
.WithHost(
"dc2.domain.com"));
});

ForRequestedType<
IAuthenticationService>()
.TheDefault.Is.OfConcreteType<
AuthenticationService>()
.TheArrayOf<
IAuthenticationService>().Contains(x =>
{
x.OfConcreteType<
Domain1AuthenticationService>();
x.OfConcreteType<
Domain2AuthenticationService>();
});

So, how to use this in your code? Like this…

 var adService = DependencyRegistrar.Resolve<IAuthenticationService>();

if (adService.Validate(tbUsername.Text, tbPassword.Text))
{
FormsAuthentication.SetAuthCookie(tbUsername.Text, true /*persist cookie*/);

if (Request.QueryString["ReturnUrl"] == null)
Response.Redirect(adService.DefaultRedirectUrl);
else
Response.Redirect(Request.QueryString["ReturnUrl"]);
}
else
//Display Error

There it is…authenticating against multiple domains. Please let me know if you know of better ways or you see ways to improve. Thanks for reading!

Shout it

kick it on DotNetKicks.com

Wednesday, July 14, 2010

Slow Down Filter for Throttling Requests with ASP.NET MVC




I’ve been looking for a good filter to play with and finally found one today. Basically, I want to slow down requests to prevent people and/or other mechanisms from submitting a form.

I saw a post by the guy’s at stackoverflow.com and ended up using a couple of their ideas in my filter attribute. Stackoverflow’s stops the execution of a request and I only want to slow it down. Generally speaking, I want to deter people/things from attempting to submit multiple times in a row after a specified amount of requests.

So…here’s the filter attribute:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class SlowDownAfterAttribute : ActionFilterAttribute
{
public const string AttemptsExceeded = "AttemptsExceeded";
private readonly int _numberOfAttemptsAllowed;
public string Name { get; set; }

public SlowDownAfterAttribute(int numberOfAttemptsAllowed)
{
_numberOfAttemptsAllowed = numberOfAttemptsAllowed;
}

public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Controller.ViewData[AttemptsExceeded] =
false;

var key = string.Concat(Name, "-", filterContext.HttpContext.Request.UserHostAddress);
var numberofattempts = Convert.ToInt32(HttpRuntime.Cache[key]);
numberofattempts++;

HttpRuntime.Cache.Insert(key,
numberofattempts,
null,
DateTime.Now.AddMinutes(2),
Cache.NoSlidingExpiration,
CacheItemPriority.Low,
null);

if (numberofattempts <= _numberOfAttemptsAllowed)
return;

filterContext.Controller.ViewData[AttemptsExceeded] =
true;
System.Threading.
Thread.Sleep(numberofattempts * 1000);
}
}

Here’s how you could use it:

[HttpPost, ValidateModel(typeof(Form)), ValidateAntiForgeryToken, SlowDownAfter(5 /*attempts*/)]
public ActionResult Validate(Form form)
{
if (!ModelState.IsValid)
//Display Error

if ((bool)ViewData[SlowDownAfterAttribute.AttemptsExceeded])
//Display Message or something

return View(form);
}

If you want to use the SlowDownAfter() attribute on multiple methods, you’d want to name it like this:

SlowDownAfter(5 /*attempts*/, Name="ValidateForm")

So basically the way this thing works is like this:

  • Sets the AttemptsExceeded flag on the ViewData to false
  • Sets the key (I got the naming convention from the StackOverflow response on stackoverflow.com)
  • Gets the number of attempts from cache
  • Incremements by 1
  • Adds the value to cache with an absolute expiration of now + 2 minutes
  • Checks if number of attempts is less than or equal to the number of attempts allowed
  • If number of attempts allowed was exceeded, it writes the ViewData AttemptsExceeded flag to true and sleeps for the number of attempts in seconds

Let me know what you think. Thanks for reading!


Shout it

kick it on DotNetKicks.com

Monday, June 14, 2010

jQuery AJAX with ASP.NET Web Forms & Sitefinity



My team and I are working with Sitefinity to create our new portal. Well, since Sitefinity doesn’t currently support MVC, we’re having to use the “good” ole web forms. Sitefinity is a really nice CMS and allows for full customization and we’re still learning how to handle the custom user controls. So…this is how we’re doing it right now.

Download Source

We wanted to allow our users to add quick links to their home screen, so that’s the sample I’m going to use. First, I created two projects, one called UserControlSample.Core (class library) and the other called UserControlSample.UI (a blank Web project).

I started with the domain model, which looks like this:

public class QuickLink
{
public Guid Id { get; set; }
public string DisplayName { get; set; }
public string URL { get; set; }
public string UserId { get; set; }

public string BuildHyperlink()
{
return string.Format("<a href=\"{0}\" title=\"{1}\">{1}</a>", URL, DisplayName);
}
}

Generally, I wouldn’t put the UserId in this class, but for this example it just made it easier.

After the domain model, I added the repository interface, which is this:

    public interface IQuickLinkRepository
{
QuickLink[] GetAllBy(string userId);
void Add(QuickLink quickLink);
void Delete(Guid quickLinkId);
}

You can download the project to see the implementation of the interface. I used L2S and again, you can download the project to see the implementation. Alrighty, so let’s get to the real code that you can use.

I added a user control, which looks like this:

<div id="quicklinks">
<
div>
<
div class="loader" id="loading" style="display: none"></div>
<
asp:Repeater ID="rQuickLinks" runat="server">
<
HeaderTemplate><h3>Quick Links</h3><ul></HeaderTemplate>
<
ItemTemplate><li id="li<%# ((QuickLink)Container.DataItem).Id %>"><img src="/_a/i/ico-delete.png" width="16" height="16" alt="Delete <%# ((QuickLink)Container.DataItem).DisplayName %>" id="<%# ((QuickLink)Container.DataItem).Id %>" /><%#((QuickLink)Container.DataItem).BuildHyperlink() %></li></ItemTemplate>
<
FooterTemplate></ul></FooterTemplate>
</
asp:Repeater>
<
a href="#" id="addQuickLink" title="Add Quick Link">Add Quick Link</a>
</
div>
<
div style="display: none">
<
fieldset>
<
legend>Add Quick Link</legend>
<
p><label for="<%=tbDisplayName.ClientID %>" title="Display Name:">Display Name:</label><br />
<
asp:TextBox ID="tbDisplayName" runat="server" /> <asp:RequiredFieldValidator ValidationGroup="addQuickLink" ControlToValidate="tbDisplayName" Display="Dynamic" ID="rv1" SetFocusOnError="true" runat="server">*</asp:RequiredFieldValidator></p>
<
p><label for="<%=tbURL.ClientID %>" title="Website Address:">Website Address:</label><br />
<
asp:TextBox ID="tbURL" runat="server" /> <asp:RequiredFieldValidator ValidationGroup="addQuickLink" ControlToValidate="tbURL" Display="Dynamic" ID="rv2" SetFocusOnError="true" runat="server">*</asp:RequiredFieldValidator> <asp:RegularExpressionValidator ControlToValidate="tbURL" Display="Dynamic" ID="re1" runat="server" SetFocusOnError="true" ValidationGroup="addQuickLink" ValidationExpression="http(s)?://([\w-]+\.)+[\w-]+(/[\w- ./?%&amp;=]*)?">*</asp:RegularExpressionValidator></p>
</
fieldset>
<
asp:Button ID="bSubmit" runat="server" CausesValidation="true" Text="Add Quick Link" ValidationGroup="addQuickLink" OnClick="AddQuickLink" /> <a href="#" title="Cancel Quick Link Addition" id="cancelAddQuickLink">Cancel</a>
</
div>
</
div>

That looks really messy, but that’s web forms for ya :D

Here’s the interesting part, instead of having a code-behind and inheriting from a partial, we added a class to the Core.UI.Controls called QuickLinksControl. Here’s the code:

 

public class QuickLinksControl : BaseControl
{
private IQuickLinkRepository _repository;

protected void Page_Load(object sender, EventArgs e)
{
_repository = DependencyResolution.
DependencyRegistrar.Resolve<IQuickLinkRepository>();

if (Request.Form["QLID"] != null)
DeleteQuickLink(
new Guid(Request.Form["QLID"]));

if(!Page.IsPostBack)
LoadQuickLinks();
}

private void DeleteQuickLink(Guid quickLinkId)
{
_repository.Delete(quickLinkId);
Response.Write(quickLinkId);
Response.End();
}

protected void AddQuickLink(object sender, EventArgs e)
{
if (!Page.IsValid)
return;

var tbDisplayName = (TextBox)Page.FindControl("tbDisplayName");
var tbURL = (TextBox)Page.FindControl("tbURL");
var button = (Button) sender;
var quickLink = new QuickLink {DisplayName = tbDisplayName.Text, URL = tbURL.Text, UserId = button.CommandArgument};
_repository.Add(quickLink);

tbDisplayName.Text =
"";
tbURL.Text =
"";

LoadQuickLinks();
}

private void LoadQuickLinks()
{
var rQuickLinks = (Repeater) FindControl("rQuickLinks");
rQuickLinks.DataSource = _repository.GetAllBy(Page.User.Identity.Name);
rQuickLinks.DataBind();
}
}

The interesting parts…

  1. DeleteQuickLink() is checked in the Page_Load. We’re doing this here for an AJAX call from the ascx file. We’re probably going to add some type of validation token to the if statement as well. We also might create some type of AJAX call handler instead of throwing them in the page_load, but it works for now.
  2. In the DeleteQuickLink method, we’re calling Response.Write(quickLinkId) and Response.End(). This is used for the AJAX call too to return the guid sent. There’s probably a better way, but so far this is working.
  3. In the AddQuickLink, you’ll notice that we’re having to find the controls because it’s not a partial and doesn’t know anything about the ascx page. This part kinda stinks, but it’s not too bad.

Here are the AJAX calls from the ascx page:

<script language="javascript" type="text/javascript">
$(document).ready(
function() {
$(
"#addQuickLink,#cancelAddQuickLink").click(function() {
$(
"div#quicklinks div").toggle();
});

$(
"#loading").ajaxStart(function() {
$(
this).show().text("Deleting...");
}).ajaxStop(
function() {
$(
this).hide().text("");
});

$(
'#quicklinks ul li img').click(function() {
if (!confirm('Are you sure you want to delete?'))
return;

$.post(window.location.href, { QLID: $(
this).attr('id') }, function(data) {
$(
"#li" + data).remove();
});
});
});
</
script>

I’ve never used the ajaxStart or ajaxStop before, but they’re pretty cool features. I also wanted to note that I started using StructureMap 2.6.1 and I’m liking it. It’s a pretty big difference from the last blog post on StructureMap. Now the ForRequestedType<> in the registry looks like this:

For<IQuickLinkRepository>()
.Use<
QuickLinkRepository>()
.Ctor<
string>("connstring")
.Is(
@"Data Source=.\SQLEXPRESS;AttachDbFilename=""..\LearnerSample.mdf"";Integrated Security=True;User Instance=True");

 

Here’s the finished product:

image

image 

So, any thoughts or ideas on what we’re doing? I’d appreciate any advice or comments. It’s probably easier to understand by downloading the sample code.

Thanks for reading!

Download Source

Shout it

kick it on DotNetKicks.com

Related Posts Plugin for WordPress, Blogger...