230 likes | 369 Views
Snabbare/enklare utveckling med Webfoundation (RAD). Förslag som med framgång redan har smygits in i en del kund projekt, testats och givit snabbare utveckling. Konfigurerbar model ( Mapper ) med alternativt factory lager. Mapper som alternativt Factory lager Kontrollerbaserad MVC
E N D
Snabbare/enklare utveckling med Webfoundation (RAD) • Förslag som med framgång redan har smygits in i en del kund projekt, testats och givit snabbare utveckling. • Konfigurerbar model(Mapper) med alternativt factory lager. • Mapper som alternativt Factory lager • Kontrollerbaserad MVC • Autoregistrering i Autofac • Använda standard MVC i största utsträckning eller open source alternativ, t.ex. form validation,js,cssminifiering etc. • En fördel med dessa tekniker är att man kan smyga in dessa tekniker i nyutveckling på befintliga WF projekt.
Konfigurerbar produkt modellVad • Konfigurerbart kunna välja vilken data man vill ha ut i vyn för produkter. • T.ex. kan utvecklare eller kund (med viss risk ) konfigurerbart välja vilken data som behövs i vyn för en produkt sida eller produktlistning eller relaterade produkter, carten etc. • Borde utökas för att klara alla Enova objekt som adress mm.
Konfigurerbar produkt modellFördelar • Snabbare/enklare utveckling (slipper överlagra faktories, modeller och vill du t.ex. läsa av pris finns det troligen redan en klass för detta) • Går att anpassa model strukturen mer då det är mer otypat (lugn i en vy kan man enkelt ändra). • Bättre prestanda då allt automatiskt cachas och cachningen går att konfigurerbart kund anpassa. (T.ex. kan man välja att inte cacha pris men produkt namn beroende på språk. Vidare så riskerar man inte att läsa av properties som inte behövs för specifik kund. • Enklare att återanvända då kund specifika properties separeras ut i en egen klass. • Möjliggör att en specifik sida som t.ex. en produktlistning kan innehålla olika properties beroende på produkt. (Förutom att konfigurera önskade properties kan man programmatiskt välja properties) • Oberoende av CMS.
Konfigurerbar produkt modell flödet för t.ex. en produktlistning. CMS Produktlistningssida ProductlistService http://www.. Produkt kategori x ProductlistController RenderAction Aktuella Enova produkter Produkt x1, pris,lager Produkt x2 , pris,lager Produkt x3, pris,lager Produkt x4, pris,lager Produkt x5 , pris,lager Produkt x6, pris,lager Produkt x7 , pris,lager Hjälp jag behöver html’en för produktlistningen ProductlistMapper (Factory) Model för produktlistningen Vy och model för CMS’en Otypad (Dictionary) Model (ProductConfigViewModel) för varje produkt ProductConfigMapper CmsProductlistController Hjälp ge mig modellen för varje produkt i produktlistningen med de properties som är konfigurerade Property värdet Dessa properties skall läsas av och de skall läsas av t.ex. på bara produkten och/eller varianter etc. EnovaPropertyMapper ProductConfigurationService PriceMapper Dessa produkter skall du läsa av properties på UrlMapper LookupVaryByProductService ImageMapper För tillfället konfigureras allt i en xml fil (runtimesettings). Men alternativa servicar som läser från Backoffice kommer/kan enkelt skapas. Man kan enkelt lägga till egna kund anpassade propertymappers. EnovaPropertyMapper kan läsa vilken property som hellst som finns på enova objektet medan t.ex. PriceMapper bara kan läsa av price.
Konfigurerbar produkt modellProperty mapper • INamedPropertyMapper används för mappers som klarar av en specifik property som t.ex. pris. • IPropertyMapper klarar av properties beroende på produkt eller konfigurering. T.ex. Enovaproperties, bild urler till olika storlekar som konfigureras med ett namn. • Allting anropas med: _productConfigMapper.Map(WebFoundationProduct, pageId)
Konfigurerbar produkt modellProperty mapper • Ett exempel på hur man gör en propertymapper som returnerar true eller false om produkten finns i lager. (Notera att kravet att det skall vara en lista av strängar troligen kommer att ändras.) • publicclassIsInStockMapper : INamedPropertyMapper • { • privatereadonlyIWarehouseService _warehouseService; • privatereadonlyIsInStockMapperSettings _settings; • publicIsInStockMapper(IWarehouseServicewarehouseService,IsInStockMapperSettingssettings ) • { • _warehouseService = warehouseService; • _settings = settings; • } • publicstringPropertyName • { • get { return"Stock"; } • } • publicList<string> Map(WebFoundationProductproduct,stringpageId) • { • //TODO borde in iWarehouseServicen • doublestockQuantity = 0; • if (product.IsVariantOwner) • { • stockQuantity = product.GetVariantMembers<WebFoundationProduct>().Sum(p=>_warehouseService.GetStockQuantity(p)); • } • else • { • stockQuantity = _warehouseService.GetStockQuantity(product); • } • returnnewList<string> { (stockQuantity > _settings.InStockTreshold).ToString(CultureInfo.InvariantCulture) }; • } • publicstringDefaultVaryBy • { • get { returnnull; } • } • }
Konfigurerbar produkt modellxml konfiguration • För tillfället stöds bara xml konfiguration men backoffice stöd är önskvärt också. • I runtimme_settings.xml ställer man in följande exempel:<settingkey=“Location.Properties.PageIdentifier"value="Name,ID,listImage,Url,IsNewProduct,MoreVariants,Category"type="string" /> • Location anger man i koden t.ex. produktsida, produktlistning, relaterade produkter dvs plats i siten. PageIdentifier är valfrit, är man på en sida som inte är konfigurerad går den på inställningar för t.ex.Location.Properties • Man kan ställa in att i produktlistningen skall t.e.x. Name läsas av även på dess varianter med: <settingkey=“productList.varyBy.Name"value=“Variants"type="string" /> • För tillfället finns följande alternativ som man kan utöka genom arv eller interface (LookupVaryByProductService): publicHashSet<WebFoundationProduct> GetVaryByProductList(WebFoundationProductproduct, stringvaryBy) { switch (varyBy) { case"Product": returnnewHashSet<WebFoundationProduct>() { product }; case"ProductOrVariants": if (product.IsVariantOwner) returnnewHashSet<WebFoundationProduct>(m_productService.GetVariants(product)); returnnewHashSet<WebFoundationProduct>() { product }; case"VariantDefault": returnnewHashSet<WebFoundationProduct>() { m_variantService.GetDefaultVariant(product) ?? product }; case"Variants": returnnewHashSet<WebFoundationProduct>( m_productService.GetVariants(product) ); case"ProductAndVariants": varproductList= newHashSet<WebFoundationProduct>(m_productService.GetVariants(product)); productList.Add(product); returnproductList; default: returnnull; } }
Konfigurerbar produkt modellLathund • Fråga: Jag vill läsa av en property som det inte finns stöd för idag.Svar: Skapa en propertymapper genom att implementera IPropertyMapper eller INamedPropertyMapper • Fråga: jag vill läsa av samma properties på alla varianter eller produkter som ingår i ett paket.Svar: Ärv/implementera (I)LookupVaryByProductServiceEller skapa en ny propertymapper som slår upp de andra produkterna och läser propertyn på dem också. • Fråga: Jag vill special anpassa cachningen själv.Svar: Implementera IPropertyValuesCacheService eller ärv/implementera (I)ProductConfigMapper. • Fråga: Jag vill inte bara returnera en dictionary med strängar utan en mer komplicerad objekt hiarki som produktens attribut kategoriserad på attributtyp.Svar: Skapa en specifik propertymapper för det som inte returnerar en sträng. • Fråga: Jag har redan en typad modell och vill inte behöva skriva om allt.Svar: Det går att kombinera med befintliga kundlösningar genom att du på din befintliga model lägger till en property med typen ProductConfigViewModel och anropar ProductConfigMapper.Map. • Fråga: jag vill läsa av vissa properties på produkten när jag är i produktlistningen och vissa på produkt sidan.Svar: Man kan namnge olika sammanhang kallat location och t.om konfigurera per sida. • Fråga: När är det inte lämpligt att använda detta?Svar: För tillfället stöds inte formulär data med detta då man måste göra en ny modelbinder som förstår den otypadedatan. Eller när affärslogik måste läsa av modellen. • Fråga: Kan inte prestandan bli dålig om jag måste slå upp samma Enova objekt flera gånger.Svar: Mellanlagra i httpcontext som t.e.x: publicclassAttributeValuesMapper : IPropertyMapper { protectedEnovaAttributeTypeGetAttributeType(Contextcontext, string property) { varkey = "AttributeValuesAsStringMapper." + property; if (HttpContextFactory.Current.Items.Contains(key)) returnHttpContextFactory.Current.Items[key] asEnovaAttributeType; vartype = context.FindObject<EnovaAttributeType>(property); HttpContextFactory.Current.Items[key] = type; returntype; }
Mapper (Factory) • Mapper är ett försök till att förenkla Factory lagret i Webfoundation. • Motsvarar ungefär Factory.Create. • Factory namnet kan lätt förväxlas med design patternet medans Mapper begreppet är inarbetat som t.e.x. AutoMapper. Namnet förklarar mer vad det gäller. Skapa och mappa data från Enova till en vy model. • Modell klassen innehåller ingen kod bara properties. Pg.a. detta behöver modell klassen inget interface (Förutom om properties med validations attribut behöver variera med kundlösningar). • Model klassens properties bör alltid vara för formaterade strängar som url, förformaterade priser etc. Detta för att få bort logik i vyn och effektivisera cachning. • Mapper är inte singelton vilket gör att man enklare kan mellan lagra data som är jobbig att hämta i klassen. T.ex.. CartMapper vill kanske spara undan aktuell valuta etc istället för att slå upp det för varje cart rad. • Mappers har bara en Metod: viewModelMapper.Map(EnovaObject). • Man skapar en mapper per model klass för singel responsible. Så CartMapper kan anropa CartItemMapper. • Stödjer Autofac på ett bättre sätt. • Mappers för anropa services mm. Man bör sträva efter att ha affärslogik i service klasserna och bara presentations logik i mappern. • Man bör försöka hålla sig till max två nivåer av mappers som anropar varrandra. Ett sätt är att platta ut model vyn och inte spegla enova objekt hiarkin rakt av. Eller anropa renderaction om modellen under inte är kopplad till huvud modellen.
MapperAnrop • Mappers anropar man i sin controller genom att först anropa en service för att hämta grunden till sin enova data och sedan anropar man mappernsMap metod. publicclassCartController: Controller { privatereadonlyICartService _cartService; privatereadonlyICartMapper _mapper; publicCartSurfaceController(ICartServicecartService, ICartMappermapper) { _cartService = cartService; _mapper = mapper; } publicPartialViewResultMiniCart() { varcart = _cartService.GetCartOrCreateNew(); varmodel = _mapper.Map(cart); returnPartialView(model); } }
MapperCartMappermodel publicclassCartViewModel{ publicList<ICartItemViewModel> CartItems { get; set; } publicstringTotalPriceWithCurrency { get; set; } } publicclassCartItemViewModel : ICartItemViewModel { publicintProductID { get; set; } publicstringProductArtNr{ get; set; } publicstring Name { get; set; } publicstring Description { get; set; } publicstringPriceWithCurrency{ get; set; } }
MapperCartMapper publicinterfaceICartMapper { CartViewModelMap(EnovaCartcart); } publicclassCartMapper : ICartMapper { privatereadonlyFunc<CartViewModel> _createCartModel; privatereadonlyICartItemMapper _cartItemMapper; publicCartMapper(Func<CartViewModel> createCartModel, ICartItemMappercartItemMapper) { _createCartModel = createCartModel; _cartItemMapper = cartItemMapper; } publicvirtualICartViewModelMap(EnovaCartcart) { varmodel = _createCartModel(); varcontext = cart.GetContext(); model.CartItems = cart.GetCartItems<CartItem>().Select(item => _cartItemMapper.Map(item)).ToList(); model.TotalPriceWithCurrency = context.AmountToString(totalPrice, context.CurrentCurrency, context.CurrentCurrency.Decimals, true, true); returnmodel; } }
MapperCartItemMapper publicinterfaceICartItemMapper { CartItemViewModelMap(CartItemcart); } publicclassCartItemMapper : ICartItemMapper { privatereadonlyFunc<ICartItemViewModel> _createCartItemModel; privatereadonlyImageService _imageService; protectedbool _showPriceIncludingTax; publicCartItemMapper(Func<ICartItemViewModel> createCartItemModel, ImageServiceimageService) { _createCartItemModel = createCartItemModel; _imageService = imageService; _showPriceIncludingTax = true; } publicvirtualICartItemViewModelMap(CartItemcartItem) { varcontext = cartItem.GetContext(); // General varmodel = _createCartItemModel(); model.Name = cartItem.Name; // Product if (cartItemisEnovaProductCartItem) { var productCartItem = cartItem asEnovaProductCartItem; model.ProductArtNr = productCartItem.ProductIdentifier; model.Quantity = productCartItem.Quantity; model.Image= _imageService.GetImage(productCartItem.Product, ImageService.ImageSize.Small); model.PriceWithCurrency= context.AmountToString(productCartItem.Product.Price, context.CurrentCurrency, context.CurrentCurrency.Decimals, true, true); } returnmodel; } }
Mapper (Factory) • Möjligen kanske alla mappers bör vara konfigurerbara mappers. • I exemplet innan med cart och cartitems kan man tänka sig en propertymapper som klarar av cartitems och som för varje item anropar ConfigMapper.
MVC kontrollbaserat • I Webfoundation kör man strikt MVC, dvs en model som typat är komplett för en hel sida, vilket har en del nackdelar.
MVC kontrollbaserat • Kör man kontroll baserat dvs där man i vyn anropar RenderAction får man en mycket enklare lösning och mer lös kopplat. • Färre nivåer i modellen och factories som anropar varrandra. • Lättare att lägga till och ta bort saker på sidan. Som t.ex. lägga till en modell för twitter på sidan eller ta bort model för varianters färger. • Alla vyerna och koden för en kontroll kan man enklare samla ihop i ett projekt eller en folder. Vilket enklare gör att man kan återanvända eller anpassa koden. • Enklare att output cacha delar av sidan.
MVC kontrollbaserat • Det betyder inte att man struntar i att separera ui från logik som i web.forms. • I exemplet med produktlistning, kan man t.e.x. lägga följande klasser i samma folder: • ProductListController med en subaction • Modell klasserna. • Eventuella servicar specifikt för denna. • Vyerna måste tyvärr läggas separat under views men kan samlas under foldern ProductList. • På så sätt ser man enklare var gemensamma funktioner ligger och man bör sträva efter att försöka kategorisera ner de under respektive kontroll folder såvida det inte är en generell funktion. Detta kan leda till att det blir enklare att stycka upp stora servicar. • Man bör dock kanske inte gå till absurdum som i web.forms kontroller som pratar med varandra och undvika renderaction i stora for loopar.
MVC kontrollbaseratExempel sida • Ett exempel på en sida och vilka kontroller den skulle kunna delas in i (hela sidan anropas av en huvud controller med sidans cms data): Sök Login Varukorg Topmeny Nyheter Produktlistning (Betyg) Reklambanner Produkt tips
MVC kontrollbaseratExempel kod • Exempel på hur det kan se ut i vyn: • <% Html.RenderAction("List", "ProductListWithFilter"); %> • <% Html.RenderAction("AccessoriesProduct", new { productId = Model.ID });%> • I Controllern (notera hur jag kan få aktuell sida som inparameter då all routing data från sidan automatiskt skickas vidare.)[ChildActionOnly] publicPartialViewResult List(intcmsid, string layout) {varpageId = cmsid.ToString();
Autoregistrering med Autofac • Målet är att utvecklaren inte skall behöva bry sig så mycket om Autofac och dess komplexitet.Utvecklaren skapar bara en klass vars namn slutar med t.ex. Service så kan han injekta servicen sen med dess interface. Vill man byta ut en klass i Webfoundation skapar utvecklaren bara en klass som implementerar samma interface elelr ärver av webfoundationklasssen (Allt utan att behöva skriva någon Autofacregistering). • Förenklare modulariseringav sin kod då man inte har registrering i en jätte modul eller applikations start. • Auto registrerar alla typer i ens projekt genom namn konventioner eller klass attribut. • Ta bort servicelocator klasser från WF och istället köra med strikt konstruktorinjection. (Inget med autoregistrering men borde göras)
Autoregistrering med AutofacExempel kod • Registrera din Autofac modul på standard sätt, fast istället för att fylla den med massa registreringar anropa RegisterAllKnownServicesInAssembly , te.x.publicclassMyModule: Module { protectedoverridevoid Load(ContainerBuilder builder) { //will autoregister all types in this assembly with naming conventionbuilder.RegisterAllKnownServicesInAssembly(this.GetType().Assembly); • Som valfria argument kan man skicka in vilka typer som inte skall registreras och vilka namn konventioner som skall gälla. Detta kan man även ställa in i web.config
Autoregistrering med Autofacweb.config • Följande web.config inställningar finns: • WebFoundation.AutoRegister.ExceptTypesGör att ingen autoregistrering kommer att ske på önskade typer (komma separerat fullständigt namn) som standard blank. • WebFoundation.AutoRegister.singletonNameConventionEndsWithAutogeristrera alla klasser som singelton vars klass namn slutar med som standard: "Service", "Repository", "Provider", "Listener","Factory" • WebFoundation.AutoRegister.newInstanceEachTimeNameConventionEndsWithAutogeristrera alla klasser att skapas upp alltid vars klass namn slutar med som standard: "Model", "Mapper", "Filter", "ViewData", "Settings" • WebFoundation.AutoRegister.httpRequestNameConventionEndsWithAutogeristrera alla klasser att skapas upp per http request vars klass namn slutar med som standard: För tillfället inga då det är lite besvärligt att http request måste vara tillgängligt och att den inte nergraderar till ny instans varje gång annars. • WebFoundation.AutoRegister.containerNameConventionEndsWithAutogeristrera alla klasser att skapas upp per scope vars klass namn slutar med som standard: inga. • Standard värdena kan ändras. Fördelen med namn konventioner är att man mer kommer att följa dem och att man får rätt livstid på objekten utan att behöva tänka på det.
Autoregistrering med AutofacAttribut • Om man inte vill använda sig av namn konvention kan man använda sig av följande klass attribut. • ExcludeFromAutoregisterKlassen följer en namn konvention men man vill inte registrera den automatiskt. • Alla följande attribut har ett name argument om man vill registrera klassen med ett namn som t.ex. en MVC controller som kan se ut så här: [AutoRegister(Named = "controller.newspage")] publicclassNewsPageController : FoundationController { • AutoRegisterAsSingelton • AutoRegisterAsNewInstanceEachTime • AutoRegisterAttributeSamma som AutoRegisterAsNewInstanceEachTime • AutoRegisterAsContainer • AutoRegisterAsHttpRequest