WPF "UWP" draws on the ASP. NET core pipeline processing model to create a picture cache control Imageex

Source: Internet
Author: User

Original: "WPF" "UWP" builds picture cache control from the ASP. NET core pipeline processing model Imageex

In WEB development, an IMG tag is used to render pictures, and in general, browsers cache those images.

For example, to visit Baidu, we can find that pictures, scripts, such as the cache (memory cache/disk cache) loaded, rather than visit a Baidu server, on the one hand to improve the response speed, on the other hand, but also reduce the pressure on the service side.

However, for WPF and UWP Development, the original image control is only memory-cached, and there is no disk cache, so once the program exits, the next time you restart the program, it still has to take pictures from the server. Therefore, it is still necessary to build an Image control with a cache, especially a disk cache.

In WPF and the UWP, we all know that the type of the Image control's Source property is ImageSource, but if we use data binding, we can bind a string, and at runtime we find that the Source property becomes a BitmapImage The object of type. Then it can be inferred that the framework has made some transformations for us. After reviewing the WPF data, it was found that ImageSource has a typeconverterattribute on this type:

View Imagesourceconverter's source code (https://referencesource.microsoft.com/#PresentationCore/core/csharp/system/windows/ MEDIA/IMAGESOURCECONVERTER.CS,0F008DB560B688FE), we can see such a

Therefore, when binding the source property, our data source is available with: string, Stream, Uri, byte[], and of course its own imagesource (BitmapImage is the subclass of ImageSource).

There are 5 of them, but ultimately what we need is imagesource. Another Uri is equivalent to a string conversion. After careful analysis, we can probably draw the following conclusions:

string–> uri–> byte[]–> stream–> ImageSource

Where URI to byte[] is equivalent to loading the image data from the URI corresponding to the location, common is the Web, disk and program embedded resources.

In some nodes we can be added to the cache, such as encountered a HTTP/HTTPS address, it can first check whether there is a local cache file, there is directly loaded not to access the server.

After finishing, basically can draw the following flowchart.

As you can see, the process is a top-down, bottom-up process. This is the equivalent of a pipeline processing model. Each line is equivalent to one pipe, and then the entire process is the same as the entire pipe in series.

During the implementation of the Code, I borrowed from the process of middleware in ASP. https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware/?view=aspnetcore-2.1&tabs= Aspnetcore2x

in ASP. NET core, one of the middleware is as follows:

 Public class aspnetcoremiddleware{    publicasync  Task InvokeAsync (HttpContext context, Requestdelegate next)    {        //  before        await  next (context);         //  After     }}

First build a context similar to HttpContext for processing in this pipeline model, and I'll call Loadingcontext:

 Public classLoadingcontext<tresult>whereTResult:class{    Private byte[] _httpresponsebytes; PrivateTResult _result;  PublicLoadingcontext (Objectsource) {        if(Source = =NULL)        {            Throw NewArgumentNullException (nameof (source)); } Originsource=source; Current=source; }     Public ObjectCurrent {Get;Set; }  Public byte[] Httpresponsebytes {Get=_httpresponsebytes; Set        {            if(_httpresponsebytes! =NULL)            {                Throw NewInvalidOperationException ("value has been set."); } _httpresponsebytes=value; }    }     Public ObjectOriginsource {Get; }  PublicTResult Result {Get=_result; Set        {            if(_result! =NULL)            {                Throw NewInvalidOperationException ("value has been set."); } _result=value; }    }}

There are four properties, Originsource represents the input's original source,current representing the current Source value, which is consistent with Originsource at the outset. Result represents the final output, generally does not require the user to manually set, only need to reach the bottom of the pipe, if the result is still empty, then assign the current value to Result. Httpresponsebytes once set, it cannot be set again.

You may ask, why do you want to httpresponsebytes this property alone, and not cache to disk when the download is complete? Here to consider the download back is not necessarily a picture, wait until the succeeding, get a ImageSource object, that can think this is a picture, this time only cache.

What's more, it's a generic type, and given the extensibility, it's not imagesource to have a Source type for an Image (* ^_^ *)

While Requestdelegate is a delegate, the signature is as follows:

 Public Delegate System.Threading.Tasks.Task Requestdelegate (HttpContext context);

So I modeled a pipedelegate delegate in the code.

 Public Delegate default where class;

Notnullattribute is from Jetbrains.annotations, the NuGet package.

In addition, Microsoft's father said that support to cancel, that is good practice, to be praised, so added the CancellationToken parameters.

Then we can prepare our own middleware, the code is as follows:

 Public Abstract classPipebase<tresult>: IDisposablewhereTResult:class{    protected BOOLIsInDesignMode = (BOOL) DesignerProperties.IsInDesignModeProperty.GetMetadata (typeof(DependencyObject)).    DefaultValue;  Public Virtual voidDispose () {} Public AbstractTask InvokeAsync ([notnull]loadingcontext<tresult> context, [notnull]pipedelegate<tresult> Next, CancellationToken CancellationToken =default(CancellationToken));}

Like the middleware of ASP. I add a IsInDesignMode property, after all, there is no need to run cache-related branches under designer mode.

So, our own middleware, that is, Pipe has, how to concatenate it, here we can see the source of ASP.

https://github.com/aspnet/HttpAbstractions/blob/a78b194a84cfbc560a56d6d951eb71c8367d17bb/src/ Microsoft.aspnetcore.http/internal/applicationbuilder.cs

         public requestdelegate Build ()        {            = context = =                {404 ;                 return task.completedtask;            };             foreach (var in _components.) Reverse ())            {                = component (app);            }             return app;        }

Where _components is defined as follows:

Private ReadOnly New List<func<requestdelegate, requestdelegate>> ();

Func<requestdelegate, the Requestdelegate> representative entered a delegate and returned a delegate. The app above is the bottom of the pipeline because it can't be processed, so it's assigned a value of 404. As for why to reverse the following table, this people can try their own manual, and here is not good analysis.

Therefore, I write down the following code to assemble our Pipe.

Internal StaticPipedelegate<tresult> build<tresult> (ienumerable<type> pipes)whereTResult:class{pipedelegate<TResult> end = (context, cancellationtoken) = =    {        if(Context. Result = =NULL) {context. Result= Context. Current asTResult; }        if(Context. Result = =NULL)        {            Throw NewNotSupportedException (); }        returnTask.completedtask;    }; foreach(varPipetypeinchpipes. Reverse ()) {Func<pipedelegate<tresult>, pipedelegate<tresult>> handler = next + =        {            return(context, CancellationToken) =            {                using(varPipe = createpipe<tresult>(Pipetype)) {                    returnPipe.                InvokeAsync (context, Next, cancellationtoken);        }            };        }; End=handler (end); }    returnend;}

The code is more complex than the ASP. NET core, first look at the initialization of the above end. Because the bottom of the pipe is reached, if the result is still empty, then try to assign the current value to result, if it is empty after execution, then the input Source is an unsupported type, it throws the exception directly.

In the following loop body, handler is equivalent to the component of the above ASP. NET core, accepts a delegate and returns a delegate.

In the delegate body, an instance is created based on the type of the current pipeline and the InvokeAsync method is executed.

The code to build the pipeline is also there, so it's not hard to load the logic.

Private AsyncTask Setsourceasync (Objectsource) {            if(_image = =NULL)            {                return; } _lastloadcts?.            Cancel (); if(Source = =NULL) {_image. Source=NULL; VisualStateManager.GoToState ( This, Normalstatename,true); return; } _lastloadcts=NewCancellationTokenSource (); Try{visualstatemanager.gotostate ( This, Loadingstatename,true); varContext =NewLoadingcontext<imagesource>(source); varPipedelegate = pipebuilder.build<imagesource>(Pipes); varRetryDelay =RetryDelay; varPolicy = policy.handle<exception> (). Waitandretryasync (retrycount, Count = RetryDelay, (ex, delay) = ={context.                Reset ();                }); awaitPolicy. Executeasync (() =Pipedelegate.invoke (context, _lastloadcts.token)); if(!_lastloadcts.iscancellationrequested) {_image. Source=context.                    Result; VisualStateManager.GoToState ( This, Openedstatename,true); ImageOpened?. Invoke ( This, Eventargs.empty); }            }            Catch(Exception ex) {if(!_lastloadcts.iscancellationrequested) {_image. Source=NULL; VisualStateManager.GoToState ( This, Failedstatename,true); ImageFailed?. Invoke ( This,NewImageexfailedeventargs (source, ex)); }            }        }

There is a need for a native Image control to be hosted in our Imageex control (otherwise, it is displayed).

Here I have defined 4 visualstate:

Normal: The case where the Source is null is not loaded.

Opened: The load succeeds and the ImageOpened event is raised.

Failed: The load failed and the ImageFailed event is raised.

Loading: Loading.

In this code, I introduced the Polly library, which is used to retry and, once an exception occurs, resets the context to its initial state and then executes the pipeline again.

The type of _lastloadcts is CancellationTokenSource, because if the Source changes rapidly, then the previous execution needs to be discarded.

Finally, the source code (including WPF and UWP demo):

Https://github.com/h82258652/HN.Controls.ImageEx

First of all, if you use the problem in real projects, I am not responsible for the said.

This article simply introduces the implementation of the specific key points, such as disk caching, Pipe service injection (get a very simple) these can refer to the implementation of the source code.

In addition, the source code should be improved in some places, I hope that we can give some good ideas and opinions, after all, personal ability is limited.

WPF "UWP" draws on the ASP. NET core pipeline processing model to create a picture cache control Imageex

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.