
If someone has a desire to make his photosite or to get acquainted with ASP.NET MVC with an example, it may be useful to have my experience in creating such a site and the source code will form the basis of my own project.
To make it clear what will be discussed, the link to the site is
www.bongiozzo.ru .
')
And now, actually, how it was done.
What did you want?
- Make a portfolio in the form of several photo albums and add context from social networks;
- I wanted to make something of my own - I initially did not look at ready-made solutions;
- As a photo storage, I immediately planned to use existing photo hosting services with an active community of photographers - services have an API, besides, photos are published for feedback, and you can get it, first of all, there;
- For the same reason, fasten the Facebook and Twitter buttons to the photos;
- To view photos, it was planned to acquire a stylish gallery that will look good on tablets;
- Well, in the end, I wanted to put all this on the best, in terms of cost / service ratio, hosting.
Task set, let's go ...
Determine the development environment and framework
First of all,
freely downloaded and installed Visual Studio 2012 RC. Web Forms and MVC are available for website development - my sympathies are entirely on the side of MVC, since This pattern is used to create sites on Perl, when Web Forms was still not there :).
As for the MVC version, there were no special thoughts either - since everything is new (VS 2012 on Windows 8), I will develop it on the latest version of MVC 4.
The main thing is content
I already had a pro account on
Flickr , although this is an absolutely optional condition. The automatic grouping and sorting of photos by albums on Flickr was also configured by me using the
SmartSetr service. Therefore, the first thing I did was open the Nuget Package Manager and, having entered Flickr, I got in the project support for the Flickr API using the Flickr.NET library.
Maybe later I will consider transferring CMS to 500px , or maybe someone will do it faster than me.The idea of using Flickr as a CMS was simple - photo albums with the Project keyword in the description will be displayed on my site (in Flickr they are called Photoset). Photos in the album will be sorted by popularity as determined by Flickr users.
Here are some simple methods for the FlickrModel data generation model.
public List<FlickrProject> GetProjects(string language) { PhotosetCollection flickrPhotosets = flickr.PhotosetsGetList(Properties.Settings.Default.FlickrUserId); Regex reg_Project = new Regex(@"^Project\s+([^\|].*)\s+\|\s+(.*)"); Match m; List<FlickrProject> returnProjects = new List<FlickrProject>(); foreach (Photoset item in flickrPhotosets) { m = reg_Project.Match(item.Description); if (m.Success) { FlickrProject prj = new FlickrProject(); prj.Description = getDescription(item.Description, language); prj.PhotosetId = item.PhotosetId; prj.Photos = GetPhotosetPhotos(item.PhotosetId, language); prj.PrimaryPhoto = prj.Photos.ToList().Find( delegate(Photo ph) { return ph.PhotoId == item.PrimaryPhotoId; }); returnProjects.Add(prj); } } return returnProjects; } public PhotosetPhotoCollection GetPhotosetPhotos(string photosetId, string language) { PhotosetPhotoCollection photos = flickr.PhotosetsGetPhotos(photosetId, PhotoSearchExtras.Description ); foreach (Photo item in photos) { item.Title = getTitle(item.Title, language); item.Description = getDescription(item.Description, language); } return photos; }
Models for the latest
Twitter posts and
Kinopoisk movies look no harder.
If you noticed - the method returns the names and descriptions of photos in different languages.
Call photos for their own or in English?
I had such a question some time ago. I decided that I would duplicate the names in Russian and English, separating them with a vertical bar. Now this naming has become possible to use on the site in the best way - if the visitor uses Russian by default in the browser settings - show him native names, otherwise English. Unlike photo hosting sites on my site it will look natural, without any dividing sticks.
MVC has a filter mechanism that applies to all or individual requests. For this task, the LocalizationAware filter was implemented, which will work on requests to the main pages of the site and set the desired language culture depending on the browser settings:
public class LocalizationAwareAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { var httpContext = filterContext.HttpContext; string language = "en"; HttpCookie langCookie = httpContext.Request.Cookies["lang"]; if (langCookie == null) { if (httpContext.Request.UserLanguages != null && httpContext.Request.UserLanguages[0].IndexOf("ru", StringComparison.OrdinalIgnoreCase) != -1) language = "ru"; httpContext.Response.AppendCookie(createCookie(language)); } else if (langCookie.Value == "ru") language = "ru"; Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(language); } public static HttpCookie createCookie(string language) { HttpCookie cookie = new HttpCookie("lang", language); cookie.Expires = DateTime.Now.AddYears(1); return cookie; } }
Of course, it is possible to change the language if the default selection does not suit:
public ActionResult setLanguage(string id) { id = id == "ru" ? "ru" : "en"; Response.AppendCookie(LocalizationAwareAttribute.createCookie(id)); return Redirect("/"); }
In addition to the names of the photos, all elements of the page - the headers and texts now also automatically switch to the selected language due to the exposed language culture and the presence in the project of two resource files - Resources.resx and Resources.ru.resx. It was not necessary to encode something additionally - this functionality is built-in.
Performance and caching
The advantages of network services are obvious, but these advantages will be negated if the response is slow, especially during peak loads. Obviously, responses from Flickr, Twitter and other systems need to be cached.
It is easy to do this - it is enough to connect the appropriate filter, specifying the OutputCache attribute before the method.
To form a page, I use several Actions — the main request (Index, for example) and several ChildActions for rendering parts of the page that contain answers from different external systems. For each type of content, you can use its own storage time in the cache, depending on the dynamics of changes.
[LocalizationAware] [OutputCache(Duration = 20 * 60, Location = System.Web.UI.OutputCacheLocation.Server, VaryByCustom = "lang")] public ActionResult Index() { return View(bag); } [ChildActionOnly] [OutputCache(Duration = 10*60*60)] public ActionResult GetProjects(string language) { HomeViewBag bag = new HomeViewBag(); FlickrContext flickr = new FlickrContext(); bag.FlickrProjects = flickr.GetProjects(language); return View(bag); } [ChildActionOnly] [OutputCache(Duration = 60 * 60)] public ActionResult GetLastPhotos(string language) { HomeViewBag bag = new HomeViewBag(); FlickrContext flickr = new FlickrContext(); bag.LastPhotos = flickr.GetLastPhotos(5, language, 1).ToList(); return View(bag); }
ASP.NET MVC itself separates cached copies of the results depending on the values of the input parameters. For ChildActions, the input parameters in the form of a language are explicitly defined, however, for basic requests, you will need to divide the cache depending on the session language, and this parameter is not clearly present in the parameters. This is implemented using the attribute VaryByCustom = "lang" and a special method GetVaryByCustomString. Now there should be no overlays in the cache.
public override string GetVaryByCustomString(HttpContext context, string arg) { if (arg.ToLower() == "lang") { string langParam = context.Request.QueryString["lang"]; HttpCookie langCookie = context.Request.Cookies["lang"]; if (langParam != null) return langParam == "ru" ? "ru" : "en"; else if (langCookie != null) return langCookie.Value == "ru" ? "ru" : "en"; return ""; } return base.GetVaryByCustomString(context, arg); }
Design and photo gallery
Because The task was originally set up - to make the site independently, but I myself was not a designer at all, it was immediately decided to make the site look as simple as possible and to limit the expressive abilities of CSS3 and HTML5. Moreover, the main focus on the photosite should go to the photos and their submission.
Therefore, I searched for available galleries and focused on the stylish
Galleria solution. Gallery is written in jQuery, has its own API, the basic edition, sufficient for expansion - is free and available for use. Exactly what is needed.
In addition to minor adjustments, the gallery initialization module added its navigation elements and integration with social networks:
this.append({ 'stage': ['closeBG', 'socialBG'] }); this.$('socialBG').html('<div id="div-fblike"></div><div id="div-twitter"></div>'); this.$('closeBG').bind("click", function (e) { window.location = "/"; });
Filling the gallery with photos looks like filling an array in JSON format with data obtained from FlickrModel.
Forming JSON in Controller:
private string getJSON(List<Photo> list, string p) { List<PhotoJSON> json = new List<PhotoJSON>(); foreach (Photo pht in list) json.Add(new PhotoJSON { thumb = pht.ThumbnailUrl, image = pht.LargeUrl, title = pht.Title, description = pht.Description, link = pht.WebUrl, url = Settings.Default.SiteURL + Url.Action("Gallery", new { controller = "Home", id = pht.PhotoId, photoset = p }) }); JavaScriptSerializer serializer = new JavaScriptSerializer(); return serializer.Serialize(json); }
JavaScript in Viewer:
var data = @Html.Raw(Model.PhotosJSON); Galleria.loadTheme('/galleria/themes/bongiozzo/galleria.bongiozzo.js'); Galleria.run('#galleria', { show: @Model.IndexOfCurrentPhoto, transition: 'slide', initialTransition: 'fade', dataSource: data });
Hanging tweets and likes
On the event of switching photos in the gallery, we update the Facebook and Twitter buttons.
this.bind("image", function (e) { document.title = galleria.getData().title; $('#div-fblike').html('<fb:like href="' + data[galleria.getIndex()].url + '" layout="button_count" send="false" show_faces="false" colorscheme="dark" />'); $('#div-twitter').html('<a href="https://twitter.com/share" class="twitter-share-button" data-url="' + data[galleria.getIndex()].url + '" data-text="' + galleria.getData().title + '">Tweet</a>'); try { FB.XFBML.parse(document.getElementById('div-fblike')); twttr.widgets.load(); } catch(ex) {} });
And do not forget to fill in the OpenGraph tags for correct display in Facebook and other OG compatible systems.
<meta property="fb:app_id" content="@Model.fb_app_id"/> <meta property="og:title" content="@ViewBag.Title" /> <meta property="og:site_name" content="@Resources.Resources.Title" /> <meta property="og:type" content="website" /> <meta property="og:url" content="@Model.SiteUrl@Url.Action("Gallery", new { controller = "Home", id = Model.GalleryPhotos[Model.IndexOfCurrentPhoto].PhotoId, photoset = Model.PhotosetId})" /> <meta property="og:image" content="@Model.GalleryPhotos[Model.IndexOfCurrentPhoto].SquareThumbnailUrl" /> <meta property="og:description" content="@Html.Raw(Model.GalleryPhotos[Model.IndexOfCurrentPhoto].Description.Replace("\n","<br/>"))" />
Where to host the project?
By the time the photosite could be launched, cloud hosting
Windows Azure with 10 free Shared sites became very useful. There is a separate cost for the database and traffic, however, given that I do not have the database and the photos themselves are stored on Flickr, and, accordingly, there is no traffic either - hosting seems almost free. At the same time, in the case of Azure, I have no questions about the quality and availability of the service, which is important. Service management is clear and simple - before Azure, I considered hosting from other providers, and this was especially evident in the contrast of confusing interface, correspondence and telephone communication with support services.
Unfortunately, there is one BUT - you cannot link your domain name to this Shared site. For those who don’t need their own domain name and need a URL like bongiozzo.azurewebsites.net, this is really the best option.
However, it
is hoped that this is a temporary restriction for Shared sites. Therefore, while I turned the site into Reserved mode, in which such a binding can be made. In any case, the 90-day Trial is still working and I have an MSDN subscription with prepay for a certain number of Azure services.
The next version of Azure Web Sites will be released - I will watch, maybe transfer to a minimal hosting of
Infobox.ru ,
Parking.ru , etc., or an
XS instance of cloud services , but for now it works, it works fine and is not worth the money.
As a conclusion
- The project turned out to be simple - just right for dating or warm-up. It took a dozen evenings to get acquainted with ASP.NET MVC 4, CSS3, jQuery and Azure;
- Sources can be taken here - bongiozzo.codeplex.com ;
- I will be glad to questions, constructive wishes and comments - @bongiozzo ;