Home
Github
Wiki
Videos
Contact
Sponsor
Handling Images and Files with property Transformers
# Property Transformers images and files can be added to your ViewModels by using `IFormFile`. property transformers are attributes which can be used to modify how data is stored and indexed. it's best to explain with an example so let's look at how images are handled in Puck by default. let's look at a simple ViewModel with an image: ```cs public class Page:BaseModel { public PuckImage Image { get; set; } } ``` the `Page` ViewModel above has a property of type `PuckImage`, which is a model which comes with Puck. let's look at `PuckImage` to see its properties. ```cs [PuckImageTransformer()] public class PuckImage { public string Path { get; set; } public string Description { get; set; } public long Size {get;set;} public string Extension { get; set; } public int? Width { get; set; } public int? Height { get; set; } public List
Crops { get; set; } public IFormFile File { get; set; } } ``` as you can see above, `PuckImage` is a pretty straightforward class with mostly properties for image metadata such as `Width` and `Height`. Notice there is also a `IFormFile` property for file upload and also notice that there is a Transformer attribute above the class, `[PuckImageTransformer()]`. this attribute will process the image when the ViewModel is saved and extract the metadata from the uploaded file. let's take a look at the source code for `PuckImageTransformer`: ```cs [AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)] public class PuckImageTransformer : Attribute, I_Property_Transformer
{ public async Task
Transform(BaseModel m,string propertyName,string ukey,PuckImage p,Dictionary
dict) { try { if (p.File == null || string.IsNullOrEmpty(p.File.FileName)) return null; string filepath = string.Concat("~/wwwroot/Media/", m.Id, "/", m.Variant, "/", ukey, "_", p.File.FileName); string absfilepath =ApiHelper.MapPath(filepath); new FileInfo(absfilepath).Directory.Create(); using (var stream = new FileStream(absfilepath, FileMode.Create)) { p.File.CopyTo(stream); } p.Path = filepath.Replace("~/wwwroot",""); p.Size = p.File.Length; p.Extension=Path.GetExtension(p.File.FileName); var img = Image.Load(absfilepath); p.Width = img.Width; p.Height = img.Height; }catch(Exception ex){ }finally { p.File = null; } return p; } } ``` it's mostly straightforward. there's a Transform method where all the action happens but first let's look at the interface `PuckImageTransformer` implements - `I_Property_Transformer
`. the first Type parameter is for the second to last argument of the Transform method and the second Type parameter is for the return type of the Transform method. in this instance, both Type parameters are of `PuckImage`, meaning that the Transform method accepts a `PuckImage` argument and returns a `PuckImage` too. now let's look at all the arguments sent to the Transform method: `BaseModel m,string propertyName,string ukey,PuckImage p`. the first argument is the current ViewModel being saved cast as a `BaseModel`, the string `propertyName` is the name of the property being transformed and the `ukey` is the unique key which is just the property name including array index if the property is inside an array - for example for a property inside an array the `propertyName` might be **Names** where the `ukey` might be **Names[0]**. Next is the property being transformed which in this case, is a `PuckImage` and the final argument is a dictionary which helps you store information that can be referenced between transformers. if you look at the body of the Transform method, it's just saving the image to a directory and setting the `Path` to the saved file and setting other metadata. ## Important - always set the IFormFile to null after processing this is good practice, although there is a default transformer that sets all IFormFile properties to null anyway. you can see the `PuckImageTransformer` doing this in the `finally{}` block. ## Puck Azure Blob Image Transformer there is a second image transformer you can use with `PuckImage` and that's the `[PuckAzureBlobImageTransformer()]`. this transformer handles images by saving them to Azure blob storage and saving a path to the file. to use this transformer, you need to set the `AzureImageTransformer_AccountName` and `AzureImageTransformer_AccessKey` AppSettings in the appSettings.json with your Azure storage account credentials. ## Re-using the same Image on multiple items of content rather than adding an image directly to your ViewModel, you can have a dedicated Image ViewModel. by default there's one in the `ViewModels` folder of the `PuckWeb` project, called `ImageVM`. this way, you can save an image as a dedicated piece of content in your site content tree, maybe in an "Images" folder and then link it to other content items using the `PuckImagePicker` content selector. you can then re-use the same image by linking it to multiple pieces of content. ## Injecting dependencies into your transformers let's take a quick look at the source for the Azure Blob Image Transformer to see an example: ```cs [AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)] public class PuckAzureBlobImageTransformer : Attribute, I_Property_Transformer
{ string accountName; string accessKey; string containerName; public void Configure(IConfiguration config) { this.accountName = config.GetValue
("AzureImageTransformer_AccountName"); this.accessKey = config.GetValue
("AzureImageTransformer_AccessKey"); this.containerName = config.GetValue
("AzureImageTransformer_ContainerName"); } public async Task
Transform(BaseModel m,string propertyName,string ukey,PuckImage p,Dictionary
dict) { //code omitted for brevity } } ``` as you can see, there's a `Configure` method and a `Transform` method. the Configure method gets called first and is passed any dependencies in the list of arguments. you can then store these dependencies in variables which you can access when the `Transform` method is called afterward. you don't need to have a `Configure` method. if it's present, it will be injected. you'll obviously need to register your dependencies in the `ConfigureServices` method of your `Startup` class.