The Ultimate Checklist for building modern websites in ASP.NET
Web page performance is always important to us. We always like to ensure that any new features that we develop keep the application running efficiently and hopefully faster, if not equal to, the speed of the last release. The Web Development Checklist is really a simple guide with a few checks that you could do before releasing a new update or feature to your side project. The checklist helps raise awareness of common best practices for building websites. It contains links to many ASP.NET-specific tools and solutions to common problems. Not only is it a great tool for all ASP.NET developers (ASP.Net & ASP.Net MVC) to learn from, but also tracks the progress of implementing the various best practices. It mainly includes: Best practices Bundling Minification Expiration Optimize responses Images Remove headers Though most of them might have already been handled in our projects, seems it’s a good one to go through. Each of the checklist items explains how it needs to be implemented. Check it on the Web Developer Checklist for the latest updates. Have questions? Contact the technology experts at InApp to learn more.
If we override equals(), then we may or may not override hashcode(). (Java override equals | Java override hashcode)
In Java, equals() is implemented in the Object class by default. This method is used to compare two objects. The default implementation just simply compares the memory addresses of the objects. You can override the default implementation of the equals() method defined in java.lang.Object. If you override the equals(), you MUST also override hashCode(). Otherwise, a violation of the general contract for Object.hashCode() will occur, which results in unexpected behavior when your class is in conjunction with all hash-based collections. This is a general contractor in Java programming that “whenever you override equals(), override hashcode() also”. Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equal comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application. If two objects are equal according to the equals() method, then calling the hashCode method on each of the two objects must produce the same integer result. It is not required that if two objects are unequal according to the equals() method, then calling the hashCode() method on each of the two objects must produce distinct integer results. Consider the following two examples. In the 1st example, I have overridden equals() only, and in the 2nd one I have implemented equals() and hashCode(), see the differences. Example 1: (java override equals() only) public class HashCodeSample { private int id; private int code; private String name; public HashCodeSample(int id, int code, String name) { super(); this.id = id; this.code = code; this.name= name; } public boolean equals(Object obj) { if (!(obj instanceof HashCodeSample)) return false; if (obj == this) return true; return this.id == ((HashCodeSample) obj).id && this.code == ((HashCodeSample) obj).code; } public static void main(String[] arr) { Map<HashCodeSample,String> m = new Hashtable<HashCodeSample,String>(); HashCodeSample h1 = new HashCodeSample(1234,8, “Test1?); HashCodeSample h2 = new HashCodeSample(1234,8, “Test2?); m.put(h1,”Hashcode Test”); System.out.println(m.get(h2)); } } Compile and run this code and this will return null. Example 2: (java override equals() and java override hashcode()) public class HashCodeSample { private int id; private int code; private String name; public HashCodeSample(int id, int code, String name) { super(); this.id = id; this.code = code; this.name= name; } public boolean equals(Object obj) { if (!(obj instanceof HashCodeSample)) return false; if (obj == this) return true; return this.id == ((HashCodeSample) obj).id && this.code == ((HashCodeSample) obj).code; } public int hashCode() { int result = 0; result = (int)(id/5) + code; return result; } public static void main(String[] arr) { Map<HashCodeSample,String> m = new Hashtable<HashCodeSample,String>(); HashCodeSample h1 = new HashCodeSample(1234,8, “Test1?); HashCodeSample h2 = new HashCodeSample(1234,8, “Test2?); m.put(h1,”Hashcode Test”); System.out.println(m.get(h2)); } } Compile and run this code and this will return the Hashcode Test What is wrong with the 1st example? The two instances of HashCodeSample are logically equal according to the class’s equals method. Because the hashCode() method is not overridden, these two instances’ identities are not in common with the default hashCode implementation. Therefore, the Object.hashCode returns two different numbers instead. Such behavior violates the “Equal objects must have equal hash codes” rule defined in the hashCode contract. Have questions? Contact the technology experts at InApp to learn more.
Wowza Adaptive Streaming Engine
Wowza is an adaptive stream engine that is used for streaming high-quality video and audio to any device. It provides live and on-demand streaming of media player technologies. It can deliver content to many popular media players such as Flash Player, Apple iPhone, iPad, iPod touch, JWPlayer, etc. Wowza Streaming Engine includes support for many streaming protocols including Adobe HTTP Dynamic Streaming (Adobe HDS), Apple HTTP Live Streaming (Apple HLS) MPEG-DASH streaming, MPEG-2 Transport Streams (MPEG-TS), Real-Time Messaging Protocol (RTMP), Real-Time Streaming Protocol (RTSP) and Real-time Transport Protocol (RTP). 1. Server installation Wowza Media Server is a Java 6 (aka 1.6) and Java 7 (aka 1.7) application and requires the installation of a Java Runtime Environment (JRE) that supports deploying Java in server environments. The JRE has everything needed to run Wowza Media Server on your system. The following Java packages can be used with Wowza Media Server: 1. Java Development Kit (JDK). 2. Java JRE. We can deploy Wowza Media Server on a 64-bit operating system with the latest 64-bit Java package (JDK, JRE). Java packages can be downloaded from the Java SE Downloads webpage. Linux This section describes how to install Wowza Media Server on Linux systems. During the installation process, the package manager will extract and install the files in the /usr/local/WowzaStreamingEngine-4.0.1 directory and the server will be installed as the root user. Debian Package Manager Systems Install sudo chmod +x WowzaMediaServer-3.6.4.deb.bin sudo ./WowzaMediaServer-3.6.4.deb.bin Uninstall sudo dpkg –-purge wowzamediaserver-3.6.4 Start Server To start the server in standalone mode on Linux, open a command shell and then enter the following commands: cd /usr/local/WowzaMediaServer/bin ./startup.sh To stop server, open a command shell and enter: cd /usr/local/WowzaMediaServer/bin ./shutdown.sh Start Engine Manager To start the server in standalone mode on Linux, open a command shell and then enter the following commands: cd /usr/local/WowzaMediaServer/manger/bin ./startmgr.sh 2. Playing video from Wowza server configuration in Wowza Streaming Engine Manager Step 1: Create a vod application from engine manager, say application name vod Select the Content Directory Use default ${com.wowza.wms.context.VHostConfigHome}/content Application-specific directory ${com.wowza.wms.context.VHostConfigHome}/content/vod Use the following directory: [absolute path of the video file] Step 2: Restart the server Step 3: Test the player by using engine manger Server : rtmp://[wowza-address]:1935/vod Stream: mp4:sample.mp4 In jwplayer jwplayer(“helloworld”).setup({ height: 420, width: 680, file: “rtmp://[wowza-address]:1935/vod/mp4:sample.mp4?, }); 3. Playing videos from other servers Media Cache configuration in Wowza Streaming Engine Manager Step 1: login Url: http://[wowza-ip-address]:8088/enginemanager log in using the user credentials Step 2: Enable Media Cache After you’ve accessed the Streaming Engine Manager, click the Server tab at the top and select Media Cache in the manager Contents pane. Enable the media cache to show the status: Enabled Step 3: Media Cache Store Configuration Media Cache Stores define where VOD file sources are cached on Wowza Streaming Engine when requested by clients. Default path of media cache is ${com.wowza.wms.context.ServerConfigHome}/mediacache It is also possible to change the location or add a new store. Step 4 : Media Cache Sources Configuration The Media Cache functionality allows content to be retrieved from three different source types, providing very flexible deployment options. The sources can be any of the following: File – Content is retrieved via standard file reads. HTTP – Content is retrieved via standard HTTP requests. S3 HTTP – Content is retrieved via standard HTTP requests that conform to S3 specifications. If you click Sources, you should then be able to see the + Add Media Cache Source button Enter the source name ex: test/ Select the Source Type There are three source type files, HTTP and amazons3 Enter the Prefix ex: amazons3/ Enter the Base Path example for amazon s3: http://s3.amazonaws.com/ example for HTTP server: http://[ip address]:[port]/ AWS Access Key ID and AWS Secret Access Key These are the key from the amazon server and this Enables re-streaming from an Amazon S3 bucket that’s not publicly available. Step 5 : Application configuration To use Media Cache within an application, you first need to select the correct application type. The correct type is VOD Edge. Add a new vod edge application and select the media cache source. Say ‘mediacache’ is the name of the vod edge application. Step 6: Restart the server Step 7: Test the player by using the engine manger To playback content when using a File Source: Use the application named media cache. Use an example File Source with the prefix amazons3. Use a file named sample.mp4 in your source location. Server: rtmp://[wowza-address]/mediacache Stream: mp4:amazons3/[bucket_name]/sample.mp4 In jwplayer jwplayer(“helloworld”).setup({ height: 420, width: 680, file:”rtmp://[wowza-address]/mediacache/_definst_/ mp4:amazons3/[bucket_name]/sample.mp4?, }); Reference: http://www.wowza.com/forums/content.php?121#mediacacheconf_wsem 4. Switch Video Bitrate For switching the multiple bitrate video, we need different bitrate video files and the src of the video is given in an xml file called .smil file. Given below is the sample .smil file for playing the video from amazon s3 using jwplayer. video src=”mp4:amazons3/[bucket_name]/sample_400.mp4? system-bitrate=”400000?/> In jwplayer jwplayer(“helloworld”).setup({ height: 420, width: 680, file:”[path of .smil file]“, }); 5. Transcoder addOn Wowza Transcoder AddOn provides the ability to ingest a live stream, decode the video then re-encode the stream to suit desired playback devices. Wowza Transcoder is a real-time video transcoding and transrating solution. Transcode Transcoding from selected non-H.264 video and non-AAC audio formatted streams to outbound H.263 or H.264 video and AAC audio; multiple bitrate streams can be created from a single input stream. Transrate Transrating incoming H.264/AAC streams to multiple bitrate outbound streams. Supported video and audio formats: Video (decoding) H.264 MPEG-2 MPEG-4 Part 2 Video (encoding) H.263v2 H.264 Audio (decoding) AAC G.711 (µ-law and A-law) MPEG-1 Layer 1/2 MPEG-1 Layer 3 (MP3) Speex Audio (encoding) AAC Wowza Transcoder AddOn is supported only with Wowza Media Server installed on 64-bit versions of Windows® or Linux® operating systems. 64-bit Java runtime is also required. Wowza Transcoder AddOn is licensed separately from Wowza Media Server. Adaptive bitrate delivery Wowza Transcoder AddOn is designed to make live adaptive bitrate delivery easy. Wowza Transcoder can ingest a single high-bitrate live stream and create multiple lower-bitrate renditions on-the-fly. These new renditions are key frame aligned to enable adaptive bitrate
Know-How’s of Visual Studio 2013 on ASP.NET MVC 5
The release of Visual Studio 2013 has unified the experience of using ASP.NET technologies. ASP.NET MVC 5 is the latest version developed with suggestions and contributions from the .NET community. ASP.NET MVC 5 comes along with the official release of Visual Studio 2013. What’s with Visual Studio 2013? Visual Studio 2013 offers a free web development environment for developing and testing next-generation standards-based web applications and services. It includes many productivity features like statement completion and IntelliSense, thus facilitating more work and less hunting for methods. It also includes powerful debugging features that enable developers to step through the codes and explore variables and data within it, fix bugs, and deploy the application. One can even step through line-by-line and get a look at what the application is doing. What showcases ASP.NET MVC 5? With ‘One ASP.NET’ a project creation wizard in MVC 5, it is possible to choose a project template and configure the authentication at the same time. Also, the seamless integration of the Web MVC project template with the new One ASP.NET is one of its kind experience. Bootstrap Framework has been made more user-friendly and customizable. It’s ideal for mobile devices because of its fast layout integration and responsiveness. ASP.NET identity has been introduced which can be used for authentication and identity management. Also, a new kind of filter called an Authentication filter is introduced to authenticate users by custom or third-party authentication providers.? In MVC 5 it’s now possible to override filters by providing an action method or controller by specifying an override filter. Attribute routing is also integrated into MVC 5 which allows for specific routes by annotating actions and controllers. Visual Studio 2013 on ASP.NET MVC 5 is a culmination of a dynamic ecosystem filled with components that empower the application with millions of lines of open-source code to use, read and learn from. Have questions? Contact the technology experts at InApp to learn more.
What is Elastic Search?
Elastic search is a real-time search and analytics engine. It is based on Apache Lucene and is open-source. It is designed to be scalable which means it is distributed and has Node Discovery in it. So it can automatically recognize other elastic search nodes and connect to them if required. It does automatic sharding, in a very simple way, it has its own identifier and just uses identifier modulo number of shards to determine what shard everything goes in. As a result of this, it can do a lot of smart things like where to route some queries and if an update comes where to put that update to make sure things are local as well. It does query distribution, so on querying one node it goes to all the nodes as well. All of those things one requires in this cloud type of world are available for free in elastic search. It has a RESTful, HTTP API with a wrapper for any language one can think of. Almost every day a new language wrapper comes out. One of the things about elastic search is that it is old JASON. It really fits the document model, because the document model uses the JASON structure. If there is a structure in a book with several authors and each author is having a last name, the elastic search will put this in a machine index so it can be searched. Elastic search is schema-less. It does field type recognition because the JASON document structure is not just strings but it can recognize a date, number, or a floating-point number. Also, if a schema is not provided it gives a field number and tries to be smart about it. Any JASON document that is put in is stored in the source document. It maintains a version number automatically, so if any update is done it increments an internal version document. Elastic search has integrated faceting, which works really fast using all the caches that are available. It adds statistical aggregates like sum, average, and number fields which are very powerful. Many of the queries that one wants to do can actually be fulfilled by this. It has many different field types, strings, all types of numeric, geospatial attachments, and arrays (arrays of numbers, arrays of strings). Among documents, it can have both sub-documents and nested documents. Elastic search assumes certain things about data, sharding, and configuration because it has a RESTful HTTP interface. It is possible to do cross-index searching and multi-document typing for all of those books/journals/documents that have authors. It is a really flexible tool that supports almost everything expected and is set to become the next evolution of search. Have questions? Contact the technology experts at InApp to learn more.
Installing the Burp SSL certificate in your browser
One of the functions of SSL is to authenticate the identity of webservers. To intercept traffic between your browser and webservers, Burp needs to break the SSL connection. This causes a security warning in your browser because it detects that it is not communicating directly with the authentic web server. Burp generates an SSL certificate for that host which is signed by the CA certificate. Burp’s CA certificate can be installed as a trusted root in your browser so that the per-host certificates are accepted without any alerts. Installing Burps SSL certificate is detailed in the following procedures. Browser making an SSL connection. Burp is to break the SSL connection. This causes a security warning in your browser because it identifies that it’s not directly communicating with the authentic web service. This how the SSL warning looks like in different browsers: IE Mozilla Firefox Chrome Safari To allow HTTPS websites to load properly they use their own certificate authority. Then creates an SSL certificate for each host you visit and signs this using the CA certificates. To prevent security warnings you should install a Burp CA certificate as a trusted root in your browser. This will cause your browser to trust the SSL connections that it makes to Burp. Installing SSL certification is simple but the details depend on your browser. IE – should first launch IE as Administrator. Then using Burp as your proxy visit any HTTPS URL and click “Continue to this website (not recommended)”. Click on ‘Certificates Error’ and ‘View Certificates’. Go to ‘Certification Path’ and select ‘PortSwingger CA’ and ‘View Certificate’. This displays the Certificate screen. Click on ‘Install Certificate’ and in the wizard click ‘Next’. Select “Place all certificates in the following store”, browse and select “Trusted Root Certification Authorities”. Click ‘Next’ and then ‘Finish’. Confirm the action and restart IE. Now you will be able to visit any HTTPS URL without any warnings. Mozilla Firefox – Using Burp as your proxy visit any HTTPS URL. Click ‘I Understand the Risks’ and ‘Add Exception’. View the certificate and from the ‘Details’ tab select ‘PortSwingger CA’, ‘Export’ the certificate, save it somewhere and close all pop-ups. Go to ‘Options’. From the pop-up select ‘Advanced’ –> ‘Encryption’ –> ‘View Certificate’. Click ‘Import’. Select the certificate that you have saved and select the check box ‘Trust this CA to identify websites.’ Click ‘Ok’ on all pop-ups to close. Now you should be able to visit any HTTPS URL without warning messages. Chrome – It uses the certificate from the trust store of your host computer. Normally, if you install Burp using the default browser of your computer, chrome will use this. Using Burp as your proxy visit any HTTPS URL and click on ‘Proceed anyway’ and click on the broken lock and view the certificate information. This will link you to the relevant settings on your host computer. Click on ”PortSwingger CA” certificate. Safari – Visit any HTTPS URL using Burp as your proxy. Click ‘show certificate’ and select ‘Portswingger CA’ certificate. Click on ‘Trust’ and select the option ‘Always Trust’. Click ‘Continue’ and enter the password, if you need to update the settings. Now you will be able to visit any HTTPS URL without warning messages. Have questions? Contact the technology experts at InApp to learn more.
Find the MIME type of a file based on the file signature
Sometimes we need to store images in a database instead of as physical files. For this purpose, the SQL Server database provides a data type called image. For the sake of simplicity, the extension of the file is also stored with image content. The extension will help to identify the MIME type when loading the content from the database. If the file extension is incorrect or not given then we cannot download the document as a known type. The solution to this problem is MIME type detection using Urlmon.dll, to find an appropriate MIME type from binary data. In Urlmon.dll, there’s a function called FindMimeFromData. public static string GetMimeType(byte[] content) { IntPtr mimeout; int MaxContent = content.Length; if (MaxContent > 4096) MaxContent = 4096; string mime = string.Empty; int result = 0; byte[] buf = new byte[MaxContent]; Array.Copy(content,buf,MaxContent); result = NativeMethods.FindMimeFromData(IntPtr.Zero, null, buf, MaxContent, null, 0, out mimeout, 0); if (result != 0) throw Marshal.GetExceptionForHR(result); mime = Marshal.PtrToStringUni(mimeout); Marshal.FreeCoTaskMem(mimeout); return mime; } [DllImport(“urlmon.dll”, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = false)] public static extern int FindMimeFromData(IntPtr pBC, [MarshalAs(UnmanagedType.LPWStr)] string pwzUrl, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.I1, SizeParamIndex = 3)] byte[ ] pBuffer, int cbSize, [MarshalAs(UnmanagedType.LPWStr)] string pwzMimeProposed, int dwMimeFlags, out IntPtr ppwzMimeOut, int dwReserved);} FindMimeFromData tests for the following MIME types: text/plain, text/html, text/xml, text/richtext, text/scriptlet, audio/x-aiff, audio/basic, audio/mid, audio/wav, image/gif, image/jpeg, image/pjpeg, image/png, image/x-png, image/tiff, image/bmp, image/x-xbitmap, image/x-jg, image/x-emf, image/x-wmf, video/avi, video/mpeg, application/octet-stream, application/postscript, application/base64, application/macbinhex40, application/pdf, application/xml, application/atom+xml, application/rss+xml, application/x-compressed, application/x-zip-compressed, application/x-gzip-compressed, application/java, application/x-msdownload FindMimeFromData does not detect word or excel file, it simply return “application/octet-stream”. To determine the MIME type of word/excel we have to compare the file content with a content set of byte sequences. private static readonly byte[] BMP = { 66, 77 }; private static readonly byte[] MSO = { 208, 207, 17, 224, 161, 177, 26, 225 }; //MSO includes doc, xlsprivate static readonly byte[] XLS = { 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 69, 120, 99, 101, 108, 0 }; private static readonly byte[] GIF = { 71, 73, 70, 56 }; private static readonly byte[] JPG = { 255, 216, 255 }; private static readonly byte[] PDF = { 37, 80, 68, 70, 45, 49, 46 }; private static readonly byte[] PNG = { 137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82 }; private static readonly byte[] TIFF = { 73, 73, 42, 0 }; public static string GetMimeType(byte[] content) { string mime = “application/octet-stream”; if (content.Take(2).SequenceEqual(BMP)) mime = “image/bmp”; else if (content.Take(8).SequenceEqual(MSO)) mime = IsOfType(content,XLS) ? “application/vnd.ms-excel” : “application/msword”; else if (content.Take(4).SequenceEqual(GIF)) mime = “image/gif”; else if (content.Take(3).SequenceEqual(JPG)) mime = “image/jpeg”; else if (content.Take(7).SequenceEqual(PDF)) mime = “application/pdf”; else if (content.Take(16).SequenceEqual(PNG)) mime = “image/png”; else if (content.Take(4).SequenceEqual(TIFF)) mime = “image/tiff”; return mime; } private static bool IsOfType(byte[] contents,byte[] pattern) { int i = 0; foreach (byte content in contents) { if (content.Equals(pattern[i])) { i++; if (pattern.Length.Equals(i)) return true; } else i = 0; } return false; } Have questions? Contact the technology experts at InApp to learn more.
Importance of Security Testing
Why Security Testing? With the cyber world becoming more and more vulnerable to attacks, security is something that cannot be compromised. In order to develop secure applications, one really needs to use a security development lifecycle. Security must be considered and tested throughout the project lifecycle of any application. What are the processes involved in Security Testing? The security testing process involves evaluating the quantum of risks within the application under test and pointing out the security vulnerabilities using various techniques and tools. By this it is possible to ensure that there is no data theft, there is no unauthorized access or there is no security compromise that has been made through assistance. Security testing involves Vulnerability scanning, Security scanning, Penetration Testing, Security Auditing, and Security Review. Vulnerability scanning is usually performed using an automated software tool that scans for the basic known vulnerability. It is an automated process performed using a vulnerability scanning tool like SARA. Next in line is Security scanning, where an assessment is done manually along with the software scanning. Although tools help in building a robust application, every tool has its own bottlenecks. That is the reason, in addition to automated scanning one is required to perform manual testing, that is going through system responses, examining the log files, error messages, error codes, and the like. The other aspect is Pen Testing or Penetration testing. A real-time simulation environment is used to perform penetration testing. It is totally a Black Box, a hacker’s approach, the way in which Hackers use it but is done in a controlled environment. It is performed internally within the organization without breaching any security terms. Security Auditing is for specific control or compliance issue. Usually, the compliance team or the risk evaluating team performs this security assessment. So, very frequent audits make the application more error-prone and less vulnerable. Finally, Security Review, which is static testing, wherein security review is performed as per the industry standards by reviewing documents, and architecture diagrams, and performing gap analysis. It is basically done for code reviews considering the architecture diagrams and documents which are very important. All these processes in security testing ensure that the applications developed are prone to any kind of security risk. Have questions? Contact the software testing experts at InApp to learn more.
How to record HTTPS with JMeter | JMeter Recording
To record HTTPS traffic, one needs to configure the browser proxy settings and JMeter proxy server. In the browser proxy server, the following changes should be made. Go to the options tab in the firefox browser and click Advanced >> View Certificates >> Authorities. Check for the Apache Software Foundation, JMeter Proxy Certificate and select that certificate, then click on the edit button and tick all the boxes and click Ok. JMeter Recording – When recording HTTPS with JMeter, do the following steps in the JMeter proxy server: 1. In HTTP Request Defaults: Test Plan >> Thread Group >> HTTP Request Defaults Server Name or IP[IP of the server] Port Number[Port number of the server] Implementation [HttpClient4] Protocol [https] Path [/] 2. In Recording Controller: HTTP Request Server Name or IP[IP of the server] Port Number[Port number of the server] Implementation [HttpClient4] Protocol [https] Path [/] Have questions? Contact the technology experts at InApp to learn more.
Backend as a Service (BaaS) in a Box
Backend as a Service (BaaS) in a Box BaasBox is an Open Source that provides a complete solution for managing the backend of web and mobile applications. Backend as a Service allows mobile app developers to set up and operate a cloud-based backend for their mobile and web apps. All the backend features are in a standalone server as in a box and the API facilitates storing and retrieving information to the server. This enables developers to focus on the front end of the application with the backend being readily available with rich features. You can access the source code from the GitHub repo. The available functions in this service are the administration console, content management, user management, push notifications, and DB management. This is best achieved using the web console which facilitates managing and performing administrative tasks. Its intuitive dashboard is separated into several sections each clearly depicting an array of utilities. The settings option lets one change the settings of the application such as password recovery, images, push notifications, and modifying its actions. Assets form an integral part of the BaasBox application. They are specific objects to store generic files, images, or a blob of JSON that forms the building block in the application model. Using BaasBox one can design and develop apps on the fly. Being an open-source it is community-driven and fully integrated into the cloud service. With all that you need in a box, enables developers to deploy their projects with just a click! Have questions? Contact the technology experts at InApp to learn more.