Tags: , , , , , , | Categories: ASP.Net, MVC, SEO Posted by Scott on 5/17/2013 12:17 PM | Comments (0)

Lowercase generated URLs

In Global.asax

public static void RegisterRoutes(RouteCollection routes)
{
    ...
    routes.LowercaseUrls = true;
    ...
}

This makes all generated URLs lowercase (rather than the default of using Controller/Action names as-is). While the ASP.net routing engine isn't case-sensitive so /Home/Index and /home/index will route to the same controller/action, different cased URLs are different resources in search engine parlance and so if you server the same content on both URLs you are technically duplicating that content and may be marked-down in the search engine rankings. As with everything search engine related there are few hard-and-fast rules but it can't do any harm to appease the search engine gods.

Over the weekend we tried to include the DateTime.AddDays() function in an Expression<Func<T, bool>> passed to NHibernate.Linq. This threw a NotSupportedException. So we had to implement the method ourselves.

Our original method was

public class Finder
{
    public static Expression<Func<Advertisement, bool>> CurrentAdvertisementsFunction = advertisementItem => advertisementItem.StartDate <= DateTime.Today && advertisementItem.StartDate.AddDays(advertisementItem.Duration) >= DateTime.Today;
    public IQueryable<ScoredAdvertisement> GetScoredAdvertisementsForCandidate(Candidate candidate)
    {
        IList<Advertisement> advertisements = this.entityManager.Get<Advertisement>().Where(CurrentAdvertisementsFunction).ToList();
    }
}

So to support DateTime.AddDays() in NHibernate we need a class that inherits NHibernate.Linq.Functions.BaseHqlGeneratorForMethod, sets its SupportedMethods property and overrides the BuildHql() method.

public class ExtendedDateAddGenerator : BaseHqlGeneratorForMethod
{
    public ExtendedDateAddGenerator()
    {
        base.SupportedMethods = new[]
        {
            ReflectionHelper.GetMethodDefinition<DateTime>(x => x.AddDays(0))
        };
    }

    public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection<Expression> arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
    {
        return treeBuilder.MethodCall("AddDays", new[]
        {
            visitor.Visit(targetObject).AsExpression(),
            visitor.Visit(arguments[0]).AsExpression()
        });
    }
}

Next we need a class that can merge our new SupportedMethodsproperty with the default supported methods supplied by the NHibernate.Linq.Functions.DefaultLinqToHqlGeneratorsRegistry

public class ExtendedLinqToHqlGeneratorRegistry : DefaultLinqToHqlGeneratorsRegistry
{
    public ExtendedLinqToHqlGeneratorRegistry()
    {
        this.Merge(new ExtendedDateAddGenerator());
    }
}

Next we need to actually state what SQL statement Hql should generate when this method is called.

public class CustomSqlDialect : MsSql2008Dialect
{
    public CustomSqlDialect()
    {
        RegisterFunction("AddDays", new SQLFunctionTemplate(NHibernateUtil.DateTime, "DATEADD(day,?2,?1)"));
    }
}

Finally we need to tell our configuration what we have a CustomSqlDialect that it should use and that we have extensions to the standard supported methods available.

static Database()
{
    var config = new AutoMappingConfiguration();
    sessionFactory = Fluently
    .Configure()
    .Database(MsSqlConfiguration.MsSql2008
        .Dialect<CustomSqlDialect>()
        .ConnectionString(mssqlConnectionStringBuilder =>
        {
            mssqlConnectionStringBuilder.Server("(local)").Database("realwebdevelopers.com").TrustedConnection();
        }))
    .Mappings(mappings =>
    {
        mappings.AutoMappings.Add(AutoMap.AssemblyOf<EntityBase>(config));
    })
    .ExposeConfiguration(configuration =>
    {
        configuration.SetProperty("current_session_context_class", "web");
        configuration.SetProperty("linqtohql.generatorsregistry", "RealWebDevelopers.Data.ExtendedLinqToHqlGeneratorRegistry, RealWebDevelopers.Data");
        new SchemaUpdate(configuration).Execute(false, true);
    })
    .BuildSessionFactory();
}

The line configuration.SetProperty("linqtohql.generatorsregistry", "RealWebDevelopers.Data.ExtendedLinqToHqlGeneratorRegistry, RealWebDevelopers.Data"); injects our new supported methods into the NHibernate configuration.

Tags: , , , , , , | Posted by Scott on 5/10/2013 2:33 PM | Comments (0)

If you read our article NHibernate POCO classes expose read-only collection you will know that all our domain types expose only read-only collection properties by returning IEnumerable<T> as the property type. Now we need to be able to add and remove items from the collection but still only expose the property as read-only. Well, simply we add two new methods to our type; AddChild(ChildType item) and, RemoveChild(ChildType item).

public class Parent
{
    private IEnumerable<Child> children = new List<Child>();

    public IEnumerable<Child> Children
    {
        get { return this.children.Select(child => child).ToList(); }
    }

    public OperationResult AddChild(Child child)
    {
        if(this.children.Contains(child))
        {
            return new OperationResult().Failed(new OperationError().Because("The child already exists"));
        }

        this.child.Add(child);
        return new OperationResult().Passed();
    }

    public OperationResult RemoveChild(Child child)
    {
        if(!this.children.Contains(child))
        {
            return new OperationResult().Failed(new OperationError().Because("This child does not exist"));
        }

        this.children.Remove(child);
        return new OperationResult().Passed();
    }

    public Parent WithChildren(IEnumerable<Child> children)
    {
        this.children = children;
        return this;
    }
}
Tags: , , , , , , , | Categories: C#, Fluent NHibernate, LINQ, NHibernate, Ninject Posted by Scott on 4/24/2013 7:27 PM | Comments (0)

So we need to be able to know that an entity is locked before we Save, Update or, Delete it and we need to know that this lock is exclusive. We have a System.Tuple keyed with a Guid that represents the lock for an entity based on the Type and identifer for that entity. We also timestamp when the lock was created and expire the lock after 20 minutes.

When a lock is requested we see if one already exists for the Type of the entity and its identifier. If one already exists then the lock fails otherwise we generated a new Guid key, create the lock in the System.Tuple and return the key to the caller.

When a caller wants to Save, Update or, Delete an entity it presents its lock key which we verify by checking that both the key exists in the System.Tuple and that it hasn't expired. If it is valid then the operation continues and the key is removed, otherwise the caller is prevented from performing the operation and an error message explaining why is passed back.

public class EntityManager : IEntityManager
{
    private static IList<System.Tuple<Guid, Type, int, DateTime>> entityLocks = new List<System.Tuple<Guid, Type, int, DateTime>>();
    private ISession session = null;

    public EntityManager(ISession session)
    {
        this.session = session;
    }

    public OperationResult Delete<T>(T item, Guid key) where T : EntityBase
    {
        if (!entityLocks.Any(entityLock => entityLock.Item1 == key && entityLock.Item2 == typeof(T) && entityLock.Item3 == item.Id))
        {
            return new OperationResult<T>().Failed(new OperationError().ForReason("Unable to obtain lock"));
        }

        using (ITransaction transaction = this.session.BeginTransaction())
        {
            try
            {
                this.session.Delete(item);
                transaction.Commit();
                entityLocks.Remove(entityLocks.First(entityLock => entityLock.Item1 == key));
                return new OperationResult().Passed();
            }
            catch (Exception ex)
            {
                transaction.Rollback();
                entityLocks.Remove(entityLocks.First(entityLock => entityLock.Item1 == key));
                return new OperationResult().Failed(new OperationError().ForReason(ex.Message));
            }
        }
    }

    public IQueryable<T> Get<T>() where T : EntityBase
    {
        return this.session.Query<T>();
    }

    public LockResult<T> GetLock<T>(T item) where T : EntityBase
    {
        DateTime transactionStarted = DateTime.Now;
        LockResult<T> lockResult = new LockResult<T>();
        if (entityLocks.Any(entityLock => entityLock.Item2 == typeof(T) && entityLock.Item3 == item.Id && entityLock.Item4 > transactionStarted))
        {
            return lockResult.Failed(LockResult.LockStatus.ItemIsLocked, entityLocks.First(entityLock => entityLock.Item2 == typeof(T) && entityLock.Item3 == item.Id && entityLock.Item4 > transactionStarted).Item4);
        }

        lock (entityLocks)
        {
            if (entityLocks.Any(entityLock => entityLock.Item2 == typeof(T) && entityLock.Item3 == item.Id && entityLock.Item4 > transactionStarted))
            {
                return lockResult.Failed(LockResult.LockStatus.ItemIsLocked, entityLocks.First(entityLock => entityLock.Item2 == typeof(T) && entityLock.Item3 == item.Id && entityLock.Item4 > transactionStarted).Item4);
            }

            Guid key = Guid.NewGuid();
            DateTime lockExpires = DateTime.Now.AddMinutes(20);
            entityLocks.Add(new Tuple<Guid, Type, int, DateTime>(key, typeof(T), item.Id, lockExpires));
            return lockResult.Success(item, key, lockExpires);
        }
    }

    public OperationResult<T> Save<T>(T item, Guid key) where T : EntityBase
    {
        if (item.Id > 0 && !entityLocks.Any(entityLock => entityLock.Item1 == key && entityLock.Item2 == typeof(T) && entityLock.Item3 == item.Id))
        {
            return new OperationResult<T>().Failed(new OperationError().ForReason("Unable to obtain lock"));
        }

        using (ITransaction transaction = this.session.BeginTransaction())
        {
            try
            {
                this.session.SaveOrUpdate(item);
                transaction.Commit();
                if (entityLocks.Any(entityLock => entityLock.Item1 == key && entityLock.Item2 == typeof(T) && entityLock.Item3 == item.Id))
                {
                    entityLocks.Remove(entityLocks.First(entityLock => entityLock.Item1 == key));
                }

                return new OperationResult<T>().Passed().WithItem(item);
            }
            catch (Exception ex)
            {
                transaction.Rollback();
                if (entityLocks.Any(entityLock => entityLock.Item1 == key && entityLock.Item2 == typeof(T) && entityLock.Item3 == item.Id))
                {
                    entityLocks.Remove(entityLocks.First(entityLock => entityLock.Item1 == key));
                }

                return new OperationResult<T>().Failed(new OperationError().ForReason(ex.Message));
            }
        }
    }
}

The lock result class

public class LockResult
{
    public enum LockStatus
    {
        [Description("The item is not locked")]
        ItemIsNotLocked = 0,

        [Description("The item does not exist")]
        ItemDoesNotExist,

        [Description("The item is locked")]
        ItemIsLocked
    }
}

The generic lock result class

public class LockResult<T> where T : class
{
    private DateTime expiresAt = DateTime.MinValue;
    private LockResult.LockStatus status = LockResult.LockStatus.ItemIsNotLocked;
    private T item = null;
    private Guid key = Guid.Empty;
    private bool result = false;

    public DateTime ExpiresAt
    {
        get { return this.expiresAt; }
    }

    public T Item
    {
        get { return this.item; }
    }

    public Guid Key
    {
        get { return this.key; }
    }

    public LockResult.LockStatus Status
    {
        get { return this.status; }
    }

    public bool Result
    {
        get { return this.result; }
    }

    public LockResult<T> Failed(LockResult.LockStatus reason, DateTime expiresAt)
    {
        this.expiresAt = expiresAt;
        this.status = reason;
        this.item = null;
        this.key = Guid.Empty;
        this.result = false;
        return this;
    }

    public LockResult<T> Success(T item, Guid key, DateTime expiresAt)
    {
        this.expiresAt = expiresAt;
        this.status = LockResult.LockStatus.ItemIsNotLocked;
        this.item = item;
        this.key = key;
        this.result = true;
        return this;
    }
}

The operation result class

public class OperationResult
{
    protected IList<OperationError> operationErrors = new List<OperationError>();
    protected bool result = false;

    public IEnumerable<OperationError> OperationErrors
    {
        get { return this.operationErrors.Select(operationError => operationError); }
    }

    public bool Result
    { 
        get { return this.result; }
    }

    public OperationResult AddReason(OperationError reason)
    {
        this.operationErrors.Add(reason);
        return this;
    }

    public OperationResult Failed(OperationError reason)
    {
        this.result = false;
        this.operationErrors.Add(reason);
        return this;
    }

    public OperationResult Passed()
    {
        this.result = true;
        return this;
    }
}

The generic operation result class

public class OperationResult<T> : OperationResult
{
    private T item = default(T);

    public T Item 
    {
        get { return this.item; }
    }

    public new OperationResult<T> Failed(OperationError reason)
    {
        base.result = false;
        base.operationErrors.Add(reason);
        return this;
    }

    public new OperationResult<T> Passed()
    {
        base.result = true;
        return this;
    }

    public OperationResult<T> WithItem(T item)
    {
        this.item = item;
        return this;
    }
}
Tags: , , , , , , | Categories: ASP.Net, C#, EPiServer CMS, MVC Posted by Scott on 1/11/2013 10:57 AM | Comments (0)

Create a new page in code

To create a new page as a child of a specific page

namespace Website.Plugins
{
    using EPiServer;
    using EPiServer.Core;
    public class DataImportJob
    {
        public static OperationResult ImportGolfCoursesForPopularVenues()
        {
            try
            {
                // get a PageData instance (PopularVenuesLandingPage : PageData) for the parent page using the DataFactory.Instance singleton
                PopularVenuesLandingPage popularVenuesLandingPage = (PopularVenuesLandingPage)DataFactory.Instance
                    .GetChildren(PageReference.StartPage)
                    .OfType<PopularVenuesLandingPage>()
                    .First();
                if (popularVenuesLandingPage == null)
                {
                    return new OperationResult().Failed("There is no popular venues landing page in the CMS");
                }

                // create a new PageData instance (CountryLandingPage : PageData) using the PageReference parentPageData.PageLink
                CountryLandingPage newCountryLandingPage = DataFactory.Instance
                    .GetDefaultPageData<CountryLandingPage>(popularVenuesLandingPage.PageLink);
                newCountryLandingPage.Name = golferWebCountryName;
                newCountryLandingPage.GolferWebCountryId =golferWebCountryId;
                newCountryLandingPage.CountryName = golferWebCountryName;
                // save the new PageData instance and publish it
                DataFactory.Instance
                    .Save(newCountryLandingPage, EPiServer.DataAccess.SaveAction.Publish, EPiServer.Security.AccessLevel.NoAccess);
                return new OperationResult().Succeeded();
            }
            catch (Exception exception)
            {
                return new OperationResult().Failed(exception.Message);
            }
        }
    }
}

 

Tags: , , , , , , , , , , | Categories: ASP.Net, C#, EPiServer CMS, MVC Posted by Scott on 1/9/2013 9:29 AM | Comments (0)

Since we have all our views built on view models (and not our business entities) we often need to pass collections of child view models into the parent view model. In EPiServer CMS everything is a page so (for example) we might have a CountryPage and we might create various RegionPages as children of the CountryPage. If we want to list the RegionPages on the CountryPage then the view model that the CountryPage view is based on needs to have a collection (IEnumerable<T>) of RegionPageModel - the view model for RegionPage. Within an EPiServer PageController<T> controller we can use the EPiServer.DataFactory singleton instance to get the child pages of a specific type.

namespace Website.Controllers
{
    using EPiServer;
    using EPiServer.Core;
    using EPiServer.Web.Mvc;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    using Website.ContentTypes;
    using Website.Models;

    public class VenuePageController : PageController<CountryPage>
    {
        [ContentOutputCache]
        public ActionResult Index(CountryPage currentPage)
        {
            IEnumerable<RegionPage> regionPages = DataFactory.Instance.GetChildren(currentPage.PageLink)
                .OfType<RegionPage>()
                /*.Where(child => child.PageTypeName == typeof(RegionPage).Name)
                .Cast<RegionPage>()*/;
            IList<RegionPageModel>regionPageModels = new List<RegionPageModel>();
            foreach (RegionPage regionPage in regionPages)
            {
                regionPageModels.Add(new RegionPageModel()
                {
                    Description = regionPage.Description
                });
            }

            CountryPageModel model = new CountryPageModel()
            {
                Regions = regionPageModels
            }

            return this.View(model);
        }
    }
}

Tags: , , , , , , , | Categories: ASP.Net, C#, EPiServer CMS, MVC Posted by Scott on 1/8/2013 3:51 PM | Comments (0)

To create a custom page type for EPiServer CMS v7 using MVC we needed to create four files; the page type itself (marked with the EPiServer.DataAnnotations.ContentType attribute and inheriting EPiServer.Core.PageData), the controller (inheriting EPiServer.Web.Mvc.PageController<T>), and view model and, a view. Here are the files for our basic page type BasicPage.

ContentTypes/BasicPage.cs

namespace Website.ContentTypes
{
    using EPiServer.Core;
    using EPiServer.DataAnnotations;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;

    [ContentType]
    public class BasicPage : PageData
    {
        public virtual string Heading { get; set; }
        public virtual XhtmlString MainBody { get; set; }
    }
}

Controllers/BasicPageController.cs

namespace Website.Controllers
{
    using EPiServer.Web.Mvc;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    using Website.ContentTypes;
    using Website.Models;

    public class BasicPageController : PageController<BasicPage>
    {
        [ContentOutputCache]
        public ActionResult Index(BasicPage currentPage)
        {
            var model = new BasicPageModel
            { 
                Heading = currentPage.Heading, 
                MainBody = currentPage.MainBody
            };
            return View(model);
        }
    }
}

Models/BasicPageModel.cs

namespace Website.Models
{
    using EPiServer.Core;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;

    public class BasicPageModel
    {
        public string Heading { get; set; }
        public XhtmlString MainBody { get; set; }
    }
}

Views/BasicPage/Index.cshtml

@model Website.Models.BasicPageModel

Basic page

@Html.PropertyFor(model => model.Heading) @Html.PropertyFor(model => model.MainBody)

We also had to add the EPiServer.Web.Mvc.Html namespace to the configuration/system.web.webPages.razor/namespaces setting in Views/Web.config

Tags: , | Categories: CSS, HTML Posted by Scott on 12/19/2012 9:00 AM | Comments (0)

We wanted to have a primary navigation with a > seperator but didn't want the work of creating a (non-scaling) background image for the items. So we took our clean HTML

<!DOCTYPE html>
<html>
<head>
    <title>Sample
    <link href="/css/screen.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <header>
        <h1>Real Web Developers
        <nav>
            <ul class="clearfix">
                <li><a href="/">Home
                <li><a href="/">Candidates
                <li><a href="/">Recruiters
                <li><a href="/">About
            </ul>
        </nav>
    </header>
...

And used CSS content attributes to insert the > character before all but the first item. To put a non-breaking space before and after each > we used an escaped \a0 sequence (you cannot put mark-up into a content attribute).

...
html header:first-of-type li {
    float: left;
}

html header:first-of-type li::before {
    content: '\a0>\a0 ';
}

html header:first-of-type li:first-child::before {
    content: '';
}...
There are probably better ways of targeting the primary navigation but since we wanted this method to be easily ported we are targeting the <li> elements within the first <header> element in the page. Usually this will be the primary navigation, if not just change the CSS selector.

Tags: , , , , , , | Categories: C#, Fluent NHibernate, LINQ, NHibernate Posted by Scott on 9/19/2012 9:36 AM | Comments (0)

The C# standard for exposing a read-only collection is to use the ReadOnlyCollection<T> type from the System.Collections.ObjectModel namespace but NHibernate doesn't play with this type at all. NHibernate will however play with IEnumerable<T> quite nicely and IEnumerable<T> is effectively read-only since it doesn't have Add() or Remove() methods. Luckily ReadOnlyCollection<T> implements IEnumerable<T>.

Exposing a read-only collection as a public property in C# is easily achieved by exposing IEnumerable<T>. Note that although IList<T>.Select() exposes IEnumerable<T> it returns IQueryable<T> which can sometimes cause a headache for consumers, calling .ToList() terminates the IQueryable<T> and all consumers should play nice. This is especially notable when a child IQueryable<T> is a property of a class managed by NHibernate.

public class Resort
{
    private IList<Hotel> hotels = new List<Hotel>();

    public virtual IEnumerable<Hotel> Hotels
    {
        get { return this.hotels.Select(hotel => hotel).ToList(); }
    }
}

But now NHibernate doesn't play but all we have to do is tell NHibernate to use the field not the accessor

config.AutoMappings.Add(
    AutoMap.Assembly(Assembly.GetAssembly(typeof(Resort))
    .Override<Resort>(resortMapping => {
        resportMapping.HasMany<Hotel>(resortType => resortType.Hotels)
            .Access.CamelCaseField();
    })));
Tags: , , , | Categories: ASP.Net, C#, MVC Posted by Scott on 6/26/2012 2:51 PM | Comments (1)

Like most developers we all like to avoid magic strings wherever possible. One place that we found to be littered with these magic strings is our controllers and to a lesser but still notable extent the C# within our views.

public class HomeController : Controller
{
    [HttpGet, ActionName("special-offers")]
    public ActionResult GetSpecialOffers()
    {
    }

    [HttpGet, ActionName("register")]
    public ActionResult GetRegister()
    {
        return View("register", new HomeRegisterViewModel());
    }

    [HttpGet, ActionName("register")]
    public ActionName PostRegister(HomeRegisterViewModel model)
    {
        if (!ModelState.IsValid)
        {
            return View("register", model);
        }

        return View("register-complete");
    }
}

Clearly some of this is down to our use of Get- and Post- prefixes on our method names but we find this very useful so we're sticking with it. Still, there're six magic strings in this file alone, one of which is the same string used four times, and you can bet that this string is also used in the 'register' view within the @Html.BeginForm() call.

So, the solution? Well, we decided that each and every controller should have a static property ActionViewNames which itself has static properties which are the magic strings.

public class HomeController : Controller
{
    public static class ActionViewNames
    {
        public static string Register = "register";
        public static string RegisterComplete = "register-complete";
        public static string SpecialOffers = "special-offers";
    }

    [HttpGet, ActionName(ActionViewNames.SpecialOffers)]
    public ActionResult GetSpecialOffers()
    {
    }

    [HttpGet, ActionName(ActionViewNames.Register)]
    public ActionResult GetRegister()
    {
        return View(ActionViewNames.Register, new HomeRegisterViewModel());
    }

    [HttpGet, ActionName(ActionViewNames.Register)]
    public ActionName PostRegister(HomeRegisterViewModel model)
    {
        if (!ModelState.IsValid)
        {
            return View(ActionViewNames.Register, model);
        }

        return View(ActionViewNames.RegisterComplete);
    }
}

We think that's much better. And it's not just easier to read. And it doesn't just make your views more strongly typed. What happens in the first instance when your beloved marketing team decide that 'register' just isn't bringing in the punters but 'sign in' is the silver bullet? And no, it's not just enough to change the hyperlinks - they want symantically correct uris too! And your program manager insists that the view file name follows convention! Nightmare. Now, what happens in the second instance? Update the static string from 'register' to 'sign-in', rename the view from Register.cshtml to Sign-In.cshtml. Err done? You could rename GetRegister to GetSignIn - only you will notice the smug look on your face.

While we were about this we noticed that our view still had horrible code like

@using(Html.BeginForm(Website.Controllers.HomeController.ActionViewNames.Register, "Home", null, FormMethod.Post, null))
{
    ....
}

Horrible? Well, maybe not - but that 'Home' magic string just smells bad. We just followed the same principle but in Golbal.asax.cs

public class Website : System.Web.HttpApplication
{
    public static class ControllerNames
    {
        public static string Home = "Home";
        public static string Security = "Security";
    }
}

Now we can update our view

@using(Html.BeginForm(Website.Controllers.HomeController.ActionViewNames.Register, Website.ControllerNames.Home, null, FormMethod.Post, null))
{
    ....
}

Yeah, yeah - it's longer but who really cares with the major improvements in maintainbility?

Another area where we find magic strings cropping up is when accessing values from the Request and Session dictionaries.

public static class RequestKeys
{
    public const string AffiliateCode = "affiliateCode";
    public const string Username = "username";
}
public static class SessionKeys
{
    public const string SecurityToken = "securityToken";
}
public class MemberController : Controller
{
    [HttpGet, ActionName(Actions.Get.SignIn)]
    public ActionResult GetSignIn()
    {
        if (Session[SessionKeys.SecurityToken] == null)
        {
            // user isn't signed in
        }
    }
}