It took me a little while, and a bit of searching, to find out how to wait for an async function call to return a value.
The below sample shows how. I'm afraid I haven't the time right now to annotate it properly. Hope it helps you just the same.
var temperatureFeedUrl = 'someUrl';
var promise = thisClass.getLatestTemperature(temperatureFeedUrl);
promise.done(function (latestTemperature) {
console.log("received new tempereature");
// call some method to do something with the returned data:
temperatureRenderer.renderLatestTemperature(latestTemperature.Temperature, latestTemperature.DeviceId);
});
}).set({ time: 5000, autostart: true });
});
getLatestTemperature: function (temperatureFeedUrl) {
console.log("getLatestTemperature called");
var latestTemperature = new Object();
latestTemperature.Temperature = -1;
var deferred = $.Deferred();
var jqxhr = $.getJSON(temperatureFeedUrl)
.done(function (data) {
latestTemperature.MeasurementDate = data.created_at;
latestTemperature.Temperature = data.field1;
latestTemperature.DeviceId = data.field2;
})
.fail(function () {
deferred.resolve(latestTemperature);
})
.always(function () {
deferred.resolve(latestTemperature);
console.log(latestTemperature.Temperature);
});
return deferred.promise();
}
mandag den 2. november 2015
søndag den 9. februar 2014
SOLID principles, in layman's terms: Dependency Inversion
Raison d'être: I set out to write about the SOLID software development principles. Specifically, my aim was/is to make these things more understandable to other developers who, much in the way as yours truly, found it troublesome to have to decipher lengthy, complex articles and books on the matter. These principles are for everyone to learn and use, but I found that they were hard to fully grasp; quite possibly because I come from a non-English speaking background. So with this series of articles I'm setting out to try and de-mystify the principles, with only the best intentions in mind. The principles apply to many layers of software development. In my articles, I specifially aim to describe them as they relate to programming. I hope it'll be of use to you. Thank you for stopping by.
This will be a 5 article-series about SOLID. SOLID is all the rage; at least as far as the job-adds I'm reading are concerned; "you are expected to honor the SOLID principles" etc. So what exactly is SOLID about? Plain and simple, it's a set of guide-lines in developing object-oriented systems. They are a group of concepts that have proven themselves valuable for a great many people coding a great many pieces of software. A tale told by your elders in software engineering, if you will, that you will want to pay heed to so you can boast on your CV that you're into the SOLID principles - and you'll be a better developer for knowing them, I promise you that. Heck, you're a better developer for simply _wanting_ to know them!
SOLI[D] - The Dependency Inversion principle
No 5 and last round of our series of articles on the SOLID principles finds us at the Dependency Inversion principle. And as wikipedia says it so well, it [quote] "refers to a specific form of decoupling where conventional dependency relationships established from high-level, policy-setting modules to low-level, dependency modules are inverted (i.e. reversed) for the purpose of rendering high-level modules independent of the low-level module implementation details..[" [/quote]. Begging your pardon? Let's try that again, in layman's terms:
The dependency inversion principle means that you'll give your classes the variables they'll need to function, as opposed to letting them declare those variables. Here's first an example of how to not follow the principle:
{
public DiskLogLocation()
{
}
public void LogMessage(string message)
{
// do something to log the message here
}
}
public class Logger
{
private DiskLogLocation logLocation;
public Logger()
{
logLocation = new DiskLogLocation();
}
public void Log(string message)
{
logLocation.LogMessage(message);
}
}
public class Program
{
private static Logger logger;
static void Main(string[] args)
{
logger = new Logger();
logger.Log("a log message");
}
In the above basic we have a Program, which instantiates a Logger()-class, which in turn instantiates - "new's up", as some say - a DiskLogLocation object. This does not follow the Depedency Inversion principle, and in a beat I'll explain why. But here's first the right way to do it, if one wishes to follow the SOLID way:
{
public DiskLogLocation()
{
}
public void LogMessage(string message)
{
// do something to log the message here
}
}
public class Logger
{
private DiskLogLocation logLocation;
public Logger(DiskLogLocation _logLocation)
{
logLocation = _logLocation;
}
public void Log(string message)
{
logLocation.LogMessage(message);
}
}
public class Program
{
private static Logger logger;
static void Main(string[] args)
{
DiskLogLocation diskLogLocation = new DiskLogLocation();
logger = new Logger(diskLogLocation);
logger.Log("a log message");
}
The above code varies from the first in as much as we instantiate the DiskLogLocation object in our Main, and pass it on to our Logger-object right there as an argument in the constructor call. That is indeed 'inverted dependency', and that's what this Dependency Inversion principle is all about: Instead of having to depend on the Logger-class to declare and instantiate the DiskLogLocation object, we do it ourselves and simply pass it on. So it follows that instead of depending on our Logger-object to do it, we've "taken the responsibility back - inverted it back to Main()"
That, in a nutshell, is the Dependency Inversion principle. And the reason for wanting to apply it is this, that you'll gain greater control of how your classes are dependent on each other. We want to strive classes having as few dependencies as possible - that is, we want to limit to the extent possible having to 'new stuff up'. Why, because every time we do that, we introduce a dependency that we later cannot alter, as it's hard-wired into the class. But when we ourselves provide the class with the dependencies - in the above case the DiskLogLocation object - it needs, then we have achieved what's popularly referred to as 'loose coupling'; that is, we can easily change things around without having to re-wire a whole bunch of stuff. Well, at least that's the idea.
The above example is - of course - heavily simplified. The Dependency Inversion principle truly comes to the fore when we're dealing with abstractions, but that sentiment is grounded in the other SOLID principles and so I won't go into extending the example with that, rather refer to my other articles on those other principles mentioned.
You'll perhaps have heard of the principle of Dependency Inversion in one of its various guises: Dependency Injection, Inversion of Control, what have you. It's all about the same thing, really, if you understand the basic premise as described in the above, you should be in the clear. If you're wondering how go to from here, there's absolutely no way around researching 'Dependency Injection Containers', or DI Containers for short. A bunch of very clever people have sat down and implemented some equally clever components that takes away all the rough work, so you can invert your dependencies to your heart's content. Some popular ones include Ninject, Castle Windsor, AutoFac; I'll venture to specifically recommend the former, Ninject.
So there you have it, the Dependency Inversion principle; don't let your classes decide which dependencies they need, decide for them and that's all they damn well get. In turn, you offer thoses classes - and your project alike - the possibility to replace those dependencies with something better, should it come along. And in an ever-changing IT climate that's pure gold, the change to adapt quickly to change.
The dependency inversion principle means that you'll give your classes the variables they'll need to function, as opposed to letting them declare those variables. Here's first an example of how to not follow the principle:
public class DiskLogLocation{
public DiskLogLocation()
{
}
public void LogMessage(string message)
{
// do something to log the message here
}
}
public class Logger
{
private DiskLogLocation logLocation;
public Logger()
{
logLocation = new DiskLogLocation();
}
public void Log(string message)
{
logLocation.LogMessage(message);
}
}
public class Program
{
private static Logger logger;
static void Main(string[] args)
{
logger = new Logger();
logger.Log("a log message");
}
}In the above basic we have a Program, which instantiates a Logger()-class, which in turn instantiates - "new's up", as some say - a DiskLogLocation object. This does not follow the Depedency Inversion principle, and in a beat I'll explain why. But here's first the right way to do it, if one wishes to follow the SOLID way:
public class DiskLogLocation{
public DiskLogLocation()
{
}
public void LogMessage(string message)
{
// do something to log the message here
}
}
public class Logger
{
private DiskLogLocation logLocation;
public Logger(DiskLogLocation _logLocation)
{
logLocation = _logLocation;
}
public void Log(string message)
{
logLocation.LogMessage(message);
}
}
public class Program
{
private static Logger logger;
static void Main(string[] args)
{
DiskLogLocation diskLogLocation = new DiskLogLocation();
logger = new Logger(diskLogLocation);
logger.Log("a log message");
}
}The above code varies from the first in as much as we instantiate the DiskLogLocation object in our Main, and pass it on to our Logger-object right there as an argument in the constructor call. That is indeed 'inverted dependency', and that's what this Dependency Inversion principle is all about: Instead of having to depend on the Logger-class to declare and instantiate the DiskLogLocation object, we do it ourselves and simply pass it on. So it follows that instead of depending on our Logger-object to do it, we've "taken the responsibility back - inverted it back to Main()"
That, in a nutshell, is the Dependency Inversion principle. And the reason for wanting to apply it is this, that you'll gain greater control of how your classes are dependent on each other. We want to strive classes having as few dependencies as possible - that is, we want to limit to the extent possible having to 'new stuff up'. Why, because every time we do that, we introduce a dependency that we later cannot alter, as it's hard-wired into the class. But when we ourselves provide the class with the dependencies - in the above case the DiskLogLocation object - it needs, then we have achieved what's popularly referred to as 'loose coupling'; that is, we can easily change things around without having to re-wire a whole bunch of stuff. Well, at least that's the idea.
The above example is - of course - heavily simplified. The Dependency Inversion principle truly comes to the fore when we're dealing with abstractions, but that sentiment is grounded in the other SOLID principles and so I won't go into extending the example with that, rather refer to my other articles on those other principles mentioned.
You'll perhaps have heard of the principle of Dependency Inversion in one of its various guises: Dependency Injection, Inversion of Control, what have you. It's all about the same thing, really, if you understand the basic premise as described in the above, you should be in the clear. If you're wondering how go to from here, there's absolutely no way around researching 'Dependency Injection Containers', or DI Containers for short. A bunch of very clever people have sat down and implemented some equally clever components that takes away all the rough work, so you can invert your dependencies to your heart's content. Some popular ones include Ninject, Castle Windsor, AutoFac; I'll venture to specifically recommend the former, Ninject.
So there you have it, the Dependency Inversion principle; don't let your classes decide which dependencies they need, decide for them and that's all they damn well get. In turn, you offer thoses classes - and your project alike - the possibility to replace those dependencies with something better, should it come along. And in an ever-changing IT climate that's pure gold, the change to adapt quickly to change.
Hope this helps you in your further career. And good luck with it, too!
Etiketter:
clean code,
CodeProject,
design patterns,
programming,
SOLID
mandag den 21. oktober 2013
5 tips on how to prepare for an IT job interview. Some general, some practical.
And now for something completely different.
Having just dealt with a number of job interviews, from the job-seeker's point of view, I can testify with some certainty as to how to prepare for one such interview. I furthermore claim some success in this, in as much as I was offered 4 jobs from the 4 interviews, so I hope some of the below tips, which I made use of myself, will prove helpful to you, too.
Tip #1. Stake the place out. Apply the same dress code for your interview.
Why #1? Because a future boss is the more likely to approve of a potential candidate if she/he seems similiar in style to the future boss in question. Goes not for everyone, but for the majority of us we like the kind of people we ourselves are. And you can take advantage of that. I'm not suggesting you sit in your car outside the place for hours on end, a simple search on the Internet, presumerably their webpage or social page, will do.
Tip #2. Print out the job add and highlight relevant sentences on it.
Why #2? Because that will keep you focused on what your strategy will be, going into the interview. And it's essential you have one - it will set you apart from the others. Maybe you applied because the job add sounded really interesting, maybe you applied because you're in damn need of a job to pay your bills; whichever is the case, you can be sure you'll be asked the question "what makes this job interesting to you". And you're not to reply 'I need a job to pay my bills', so highlight relevant sentences that bring about the essense of the job add and form a strategy for answering that question, "why did you apply for this particular job" (they never seem to think that you're likely to have applied to several others, they want to hear about what makes them special to you). For example, let's say the job add mentions "must have an interest in front-end web development". You'll highlight that and make it your strategy-call: so your answer will be "because I'm going to dedicate my career to web development, it's the future I'm certain, and this company seems to be on target to be part of that future and want to be a part of that". Or something to that effect. Again, set up a strategy for what how you will present yourself and your talents. What's your 'thing', what do you have that makes you not just one in the crowd? How can you hook that up with the job add's requirements, with the company's requirements?
P.S. You don't really need to highlight one or more sentences, that's just what I myself do as I carry that printed-out job add with me to the interview, to glance at it and the lines I highlighted just a minute or so away from attending the interview.
Tip #3. Don't drink anything prior to the interview, especially coffee.
Why #3? Okay, this is a bit personal and certainly practical, it's because - if and only if you're like me - you're likely to be a bit nervous. That's a healthy thing, it will keep you on your toes, but - and, again, only if you're like me - you'll be wanting to take a pee every damn fifteen minutes, from being nervous. So if your interview is before 12 noon, and you think you can do without, it's best not to drink anything and not risk having two minutes to go before entering the room for that interview and be really needing to go to the bathroom.
Tip #4. Prepare a show and tell of something you've done (preferably in the technology that you're about to be hired for).
Why #4. Because, even if you haven't been asked to do a show and tell, this will keep you sharp and focused, right up to the second you enter the room. It will prepare you mentally if there's suddenly a coding-test in the required job-skills, that you weren't aware of. And it will make you feel like, and exude the persona of, one who comes prepared, and your body language will reflect that. It doesn't matter that you'll never actually do a show and tell. That's not the point. The point is going in there in a confident, well-prepared manner, and that's where the show and tell, disregarding its potential use, will help you.
Tip #5. Prepare for this question, "what are your good points and what are you bad points".
Why #5. Because these are classic questions that you're likely to be asked. And for a good reason, too, much can be interpreted from the answers. As is the case with tip #4, even if you'll never be asked it's good thing to have an answer ready, as it'll keep you focused on what you want to sell yourself by. With the "what are you not good at"-question, you'll try to direct the answer towards a moot point of sorts, so as to not put yourself in a less than favorable position. Don't give them some entry point they can start to dig into and create fuss about. Instead say something like "well, I'm not good at dealing with internal power-struggles and bickering" or maybe "I'm not good at dealing with an employer who's not supportive of my desire to keep myself updated in my field". That's a show-stopper right there, they're likely to talk about how that's not the case with their company and you in turn haven't actually declared any significant bad point about yourself.
The above is what works for me, and may not necessarily work for you, but I sure hope at least one or two of the points will help you, and best of luck to you. The main point of this post is this: It's vital you prepare for that interview. Don't just go in there thinking "I aced the resumé so now they want to talk, I can just wing that, it's just a chat". You spend (hopefully) time on the application, and you should spend time on the interview as well. Spend two-three hours on it, that'll suffice. Your posture will be the better for it.
Bonus tip #1. Take a shot of sugar before the interview. The sugar-rush will add 10% to your mental capabilities for a short duration, this has been a certified exam-tip for years.
Bonus tip #2. If they ask you, 'where do you see yourself in two years?' answer 'with this company', immediately and without hesitation. They want to hear their investment will still be there in two years' time so that's the answer you need to give.
onsdag den 9. oktober 2013
Mocking session state in a asp.net mvc4 unit test using Moq
I recently spent more time than I'd liked to figure out how to mock session state within an ASP.NET MVC test project. So here's the solution to that one, so hopefully you won't spend as much time as I.
Important disclaimer: I was all over the dial to search for info, but most of what I dug up was related to asp.net mvc 3. This below solution works for me with ASP.NET MVC 4 and Moq as mocking framework.
Consider the following controller and single action-method:
public class HomeController : Controller
{
public HomeController()
{
// default constructor, usually you'd inject some repository or what not here, but for this example we'll keep it simple
}
public ViewResult TestMe()
{
System.Diagnostics.Debug.WriteLine(Session["selectedMonth"]);
System.Diagnostics.Debug.WriteLine(Session["selectedYear"]);
return View();
}
}
The action-method references the current httpcontext's Sesson collection. So if we instantiate a 'HomeController' and try to obtain a ViewResult when calling the action-method, our unit test will fail with a NullReferenceException.
So, since the action-method wants to know about the Session collection, which resides in the HttpContext for the request to the method, we need to provide a HttpContext object. The below unit test code creates a mock HttpContext object, hooks it up to a mock Session object and passes it the instantation of the controller - and the test will then pass, as now the action-method has a HttpContext to reach into and yank out the Session info.
[TestMethod]
public void TestActionMethod()
{
// create the mock http context
var fakeHttpContext = new Mock();
// create a mock session object and hook it up to the mock http context
var sessionMock = new HttpSessionMock {{"selectedYear", 2013}, {"selectedMonth", 10}};
var mockSessionState = new Mock();
fakeHttpContext.Setup(ctx => ctx.Session).Returns(sessionMock);
// ... and here's how to attach a http context identity, just in case you'll come to need that, too
var fakeIdentity = new GenericIdentity("mno@ucsj.dk");
var principal = new GenericPrincipal(fakeIdentity, null);
fakeHttpContext.Setup(t => t.User).Returns(principal);
// we'll need to hook our http context up to a controller context mock - because we can't provide our controller with the http context mock directly
var homeControllerMock = new Mock();
homeControllerMock.Setup(foo => foo.HttpContext).Returns(fakeHttpContext.Object);
// all set up, now we'll instantiate the controller and pass our controller context object into its 'controllerContext' property
var target = new HomeController()
{
ControllerContext = homeControllerMock.Object
};
// ... and the below call to the action method won't throw a nullReferenceException, because now it has a Session state to dig into
ViewResult result = target.TestMe();
// ... and so the test will pass
Assert.AreEqual(string.empty, result.ViewName);
}
Etiketter:
asp.net mvc,
c#,
HttpContextBase,
HttpSessionStateBase,
mocking,
moq,
session
mandag den 23. september 2013
Contact web api from InfoPath Designer 2013
I was having some trouble getting values from my home-brewed web api into a InfoPath Designer 2013 form, so I wanted to document the solution for those of you out there perhaps struggling with this as well.
I was calling a simple 'Hello World'-web api that would return a string. My simple aip looks like this:
However, upon calling this api in InfoPath Designer 2013, I got the following error message:
... to indicate the xml structure is invalid.
I didn't get it - when I called the service directly from my browser, valid xml was returned!:
The problem, as it turned out, was of course the one that as opposed to the xml displayed by my browsers, the InfoPath Designer application - correctly - retrieved the data by way of json, the web api's default MO.
So, in order to serve up only XML, for InfoPath to agree with, we'll strip away the web api's possibility of serving up json altogether. Modify your WebApiConfig to resemble the below, though of course take into consideration the routes you have for your own app:
Now, as we re-publish the web api service, we can try once more the url in InfoPath, which will now allow us to proceed to the next step:
Intriguingly, on a completely different note, the 'get data from http rest service' workflow component in Sharepoint Designer 2013 works off JSON, as opposed to XML! But that's a blog-entry for another rainy day.
Hope this helps someone, anyone,
thanks for reading.
I was calling a simple 'Hello World'-web api that would return a string. My simple aip looks like this:
namespace ADlookup.Controllers
{
/* model */
public class employee
{
public string employeeId { get; set; }
public string bossId { get; set; }
}
public class ADLookupController : ApiController
{
[HttpGet]
public employee getNearestBossId([FromUri] string id)
{
return new employee()
{
employeeId = id,
bossId = id
};
}
}
}
However, upon calling this api in InfoPath Designer 2013, I got the following error message:
... to indicate the xml structure is invalid.
I didn't get it - when I called the service directly from my browser, valid xml was returned!:
The problem, as it turned out, was of course the one that as opposed to the xml displayed by my browsers, the InfoPath Designer application - correctly - retrieved the data by way of json, the web api's default MO.
So, in order to serve up only XML, for InfoPath to agree with, we'll strip away the web api's possibility of serving up json altogether. Modify your WebApiConfig to resemble the below, though of course take into consideration the routes you have for your own app:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// tbe below must be enabled if this rest service is used with infopath designer 2013 - it only accepts xml data.
// thus we'll remove the json return type
var formatters = GlobalConfiguration.Configuration.Formatters;
formatters.Remove(formatters.JsonFormatter);
}
}
Now, as we re-publish the web api service, we can try once more the url in InfoPath, which will now allow us to proceed to the next step:
Intriguingly, on a completely different note, the 'get data from http rest service' workflow component in Sharepoint Designer 2013 works off JSON, as opposed to XML! But that's a blog-entry for another rainy day.
Hope this helps someone, anyone,
thanks for reading.
Etiketter:
asp.net mvc,
c#,
InfoPath,
InfoPath Designer 2013,
json,
mvc,
web api,
xml
torsdag den 5. september 2013
Prevent browser caching of css and javascript files
A modern browser will attempt to cache content as best it can, to make for a better end-user experience. This works well, but we may run into issues when we deploy a set of different files to the server, yet the client browser insists on serving up the old ones out of its cache.
So here's a tip on how to force the client browser to reload newly deployed files. You can use this with any kind of content file served over an http connection, be it css-files, javascript files, you name it.
The trick is to add an updated querystring value to the content url. For example, instead of
... we go ...
... instead. With each deployment, the 'v' value changes, which will force the client into retrieving a new version of the file on the server.
I'm myself adding the assembly version into the url. I'm speficially doing this, which works with the ASP.NET MVC framework:
The above will render as follows into the browser:
As the assembly version changes with every build or publish, the url will change accordingly - and the content will be retrieved 'fresh' from the server, as now the client can no longer find the old version with a different version number.
In order to make your assembly version number auto-increment, you'll want to change the following lines in your 'assemblyInfo.cs' file (as found in the 'Properties' folder in your project):
Should be changed to:
The asterix '*' sign will be replaced with the assembly number automatically, to increment on each build/publish.
HTH.
So here's a tip on how to force the client browser to reload newly deployed files. You can use this with any kind of content file served over an http connection, be it css-files, javascript files, you name it.
The trick is to add an updated querystring value to the content url. For example, instead of
< link media='screen and (max-width: 2560px)' href="~/Content/Site.css" rel="stylesheet" />
... we go ...
< link media='screen and (max-width: 2560px)' href="~/Content/Site.css?v=1.0.4034.343343" rel="stylesheet" />
... instead. With each deployment, the 'v' value changes, which will force the client into retrieving a new version of the file on the server.
I'm myself adding the assembly version into the url. I'm speficially doing this, which works with the ASP.NET MVC framework:
@{
string versionId = System.Reflection.Assembly.GetAssembly(typeof(timeRegistrering.Web.Controllers.HomeController)).GetName().Version.ToString();
}
< link media='screen and (max-width: 2500px)' href="~/Content/Site.css?v=@{@versionId}" rel="stylesheet" />
The above will render as follows into the browser:
< link media='screen and (max-width: 2500px)' href="/Content/Site.css?v=1.0.4996.19123" rel="stylesheet" />
As the assembly version changes with every build or publish, the url will change accordingly - and the content will be retrieved 'fresh' from the server, as now the client can no longer find the old version with a different version number.
In order to make your assembly version number auto-increment, you'll want to change the following lines in your 'assemblyInfo.cs' file (as found in the 'Properties' folder in your project):
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.1")]
[assembly: AssemblyFileVersion("1.0.0.1")]
Should be changed to:
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("1.0.*")]
The asterix '*' sign will be replaced with the assembly number automatically, to increment on each build/publish.
HTH.
Etiketter:
asp.net,
cache,
css,
invalidate,
javascript,
mvc,
no cache
tirsdag den 3. september 2013
Force Internet Explorer compatibility mode off
The Internet Explorer compatibility-mode is a rendering mode to suit older web-pages, typically below and including IE8. They're usually associated with Intranet-sites, in which legacy components often abounds.
To a web-developer it can be hugely frustrating - I know for me it certainly is - to develop that which beyond your control renders horribly in an otherwise 'healthy' IE browser.
The fix - apart from using a different browser, not always a suitable solution - is to include the following meta-tag in the page header section.
The above MUST be the very first meta-tag included within the header section, or you risk the chance of it not working to your satisfaction. As you include it, and browse to the web page with compatibility mode turned on, the tag will force your IE to load the page with the rendering engine pertaining to the browser itself, and not some legacy rendering instead.
HTH
To a web-developer it can be hugely frustrating - I know for me it certainly is - to develop that which beyond your control renders horribly in an otherwise 'healthy' IE browser.
The fix - apart from using a different browser, not always a suitable solution - is to include the following meta-tag in the page header section.
< meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" >
The above MUST be the very first meta-tag included within the header section, or you risk the chance of it not working to your satisfaction. As you include it, and browse to the web page with compatibility mode turned on, the tag will force your IE to load the page with the rendering engine pertaining to the browser itself, and not some legacy rendering instead.
HTH
Abonner på:
Opslag (Atom)


