Swashbuckle, multiple API versions, and virtual directories - asp.net-web-api

I am looking at using Swashbuckle/Swagger to document my WebAPI solution. The developer portal would be something like https://myapi.com/, while the versioned API is https://myapi.com/v1/users.
The version part of the URL maps to a virtual directory, containing the binaries and config files for v1. When version 2 ships, we create a new virtual directory under the root, so now we have https://myapi.com/v2/users/some_new_endpoint_not_in_v1. This means that save for bugfixes, there is no need for any of the binaries of older versions to be touched, which reduces the likelihood of some developer accidentally breaking backwards compatibility for our customers.
However, I can't see how to configure Swashbuckle to look at those virtual directories to get the controllers/actions and XML comments to parse. The MultipleApiVersions configuration option seems more targetted at people who throw all their supported versions into one set of binaries (either by namespaces or controller names), and not by separating them into separate processes.
Any suggestions as to how I can bend Swashbuckle to my will? Should I just install Swashbuckle into the individual virtual directories as single API versions, so the docs becomes something like https://myapi.com/v1/swagger? My portal would then do the necessary work to expose the different API versions.
Update
I did try the latter approach, and for the documentation at least, it works OK. The problem is then that the URL for the Swagger spec then becomes https://myapi.com/v1/swagger/docs/v1, and I would rather not have that second v1 in the URL. Unfortunately Swaashbuckle at least expects the version number to be in the relative path, not in the base URL.

Having those would work:
Swagger UI at the root of your API site (nothing to do with Swashbuckle),
multiple virtual directories for your versions ("v1", "v2"...)
To achieve this:
the custom discoveryPaths array in Swagger UI javascript would look like below, with added "/spec" suffix (or whatever suits you, as SwashBuckle is not handling the c.SingleApiVersion with an empty version value):
var currentUrl = 'https://myapi.com/';
window.swashbuckleConfig = {
rootUrl: currentUrl,
discoveryPaths: arrayFrom('v1/swagger/docs/spec|v2/swagger/docs/spec'),
booleanValues: arrayFrom('true|false'),
validatorUrl: stringOrNullFrom('null'),
// other settings ommitted for brevity.
oAuth2AdditionalQueryStringParams: JSON.parse('{}')
};
Removing c.EnableSwaggerUi from your Web API sub apps

Related

AsyncCrudAppService Breaks Swagger When Providing TCreateInput and TUpdateInput

I recently downloaded a Single Page Web Application (Angular) from https://aspnetboilerplate.com/Templates using 3.x target version.
I just simply added a few entities and then started to follow the steps on this page https://aspnetboilerplate.com/Pages/Documents/Application-Services
Things do work well for me to Get, List, Update, and Delete entities when my app service class is just inheriting AsyncCrudAppService<Entities.PhoneBook, PhoneBookDto, long, GetAllPhoneBooksInput>, however when it is inheriting AsyncCrudAppService<Entities.PhoneBook, PhoneBookDto, long, GetAllPhoneBooksInput, CreatePhoneBookInput, and UpdatePhoneBookInput> the swagger definition will no longer load.
GitHub Repo: https://github.com/woodman231/MyPhoneBooks
(which currently does not work and will not load Swagger page).
I can get the swagger page to load by removing CreatePhoneBookInput and UpdatePhoneBookInput from
https://github.com/woodman231/MyPhoneBooks/blob/main/aspnet-core/src/MyPhoneBooks.Application/SimpleCrudAppServices/ISimplePhoneBookCrudAppService.cs#L9
and
https://github.com/woodman231/MyPhoneBooks/blob/main/aspnet-core/src/MyPhoneBooks.Application/SimpleCrudAppServices/SimplePhoneBookCrudAppService.cs#L14
However, again I am still unable to create entities using this default implementation. Any ideas?
I have cloned your repo and run it and I figured out the error, first as I tell you in comments I verified the text log, and it said the next:
System.InvalidOperationException: Can't use schemaId "$CreatePhoneBookInput" for type "$MyPhoneBooks.SimpleCrudAppServices.Dtos.CreatePhoneBookInput". The same schemaId is already used for type "$MyPhoneBooks.PhoneBooks.Dtos.CreatePhoneBookInput"
What happenig is that you have these two classes UpdatePhoneBookInput, CreatePhoneBookInput repeated in SanokeCrudAppServices\Dtos and PhoneBooks\Dtos
You have the classes in both folders with same exact name, and thats the problem, if you change the name in whatever place the swagger definition will load without errors, I have do it like this and everything works fine!
Change the name in one of the places, and all will be working fine
Personally I don't like to use a different Dto for Create and Update for me is easier to user just one Dto for all.
Ok I figured it out. I had also made a DIY AppService and some of the DTO Class Names associated with the DIY App Service clashed with the DTO Class Names associated with the Automated Service. It was acceptable in .NET since they were in different name spaces but once the swagger definition was configured I assume that there was multiple instances of the same DTO Defition. I checked the AbpLogs table but they didn't give me much details as to the specifics of the internal server error while loading the definition. It sure would have been useful to know that.

How to design front-end to handle multiple back-end versions

In my company, we're using Spring Boot to implement backend API and React to implement frontend including Web interface and Android/iOS apps.
Since our product is an Enterprise software, customers actually have to pay to get the latest backend API to deploy on their own servers. However, our mobile apps are regularly updated on the App Store. This leads to a situation where the mobile apps on end-users' devices may be the newer version while the backend API on the customer's machine is the older one. We plan to support up to 3 minor version backward, meaning FE 5.4 will support up to backend 5.2.
The backend does have an endpoint to return the current version number. However, I'm a bit clueless as to how our frontend implementation can maintain backward compatibility with older API versions as we add new features and may introduce breaking changes in backend API.
I completely understand there might not any beautiful solutions for this problem. I'm hoping if you've gone through this pain, you can share your experiences about what you've tried, the final approach that you took and the potential pitfalls to look out for.
I'm sure myself and other people who's running into this issue would be really grateful :).
Your solution will be similar to any frontend solution that uses Feature Toggle but I can already imagine that it will not be pretty.
Basically inside your code you'll have a lot of if/else statements or some form of wrapper that does the same underneath for every piece of UI/logic/functionality that is a breaking change on version upgrade.
I'd suggest that for every layers that you have (UI, logic, API call) you should start to have switches based on version returned by backend. You'll end up with a lot of redundant looking codes and a lot of codes that looks like this. (if you support only two versions. Use switch if you have more versions)
render() {
{version === "1.0.0" ? <VersionA /> : <VersionB/>}
}
You can however, write a utility method that wraps and returns different components based on versions. By doing that you can more easily remove components that you no longer need to support in the future.
const versionSwitcher = (version, ...Components) => {
switch (version) {
case "1.0.0":
return Components[0];
case "1.1.0":
return Components[1];
}
}
This of course, increases complexity throughout all layers but given your case I can't see it being simple. A good thing to do is to try to keep your own viewModel and never pass response from API directly into component. That reduces the coupling between API and your components and will make this a little easier.

Manual Url Culture Rewriting in DotNetNuke Request

I'm working with dotnetnuke 7.
I'm not using any rewriting modules and don't want to use them.
When content localization is enabled, url has the following view:
my_training_host/en-us/my-page.aspx
I want to change manually the display url so, that instead of full culture in query, the two letter culture (neutral culture) will show:
my_training_host/en/my-page.aspx
Does anyone have similar problem ?
If you ever change your mind about using a rewriting module, UrlMaster is the module for that. If not, then DNN 7.1 now adds support for creating url providers as extensions. More details at http://www.dnnsoftware.com/blog/cid/154604/Introducing-DNN-Extension-URL-Providers
I am using Open URL Rewriter for DNN - it does exactly what you ask for with it's default installation - no further config required.
And it's open source in comparison with UrlMaster.
In case of you use version 1.3.1 of Open URL Rewriter with DNN 7.04 to get two letters only for multilingual sites you have to make sure:
you create site aliases for each language with the two letter part you desire and
enable those entries as primary aliases (you need one primary alias for each language), for example for two languages (en & fr) you need to set three primary aliases like the following:
✔ www.yourdomain.com
✔ www.yourdomain.com/en us-US
✔ www.yourdomain.com/fr fr-FR

What is the standard approach for setting up a Visual Studio 2010 solution for ASP.NET MVC 3 project

I am currently working on a ASP.NET MVC 3 project and I am setting up the solution file on VS2010.
I am not sure of what is the standard approach. I am using the following approach
Company.Dept.Data (contains the dbml file - Data Model)
Company.Dept.Business (Business logics)
Company.Dept.Web (contains ASP.NET MVC3 webapplication)
The first two are class libraries and the last one is MVC3 web application.
Anyother recommendations?
There is no single "standard" approach. It all depends on your project and what problems you are trying to solve with the software. Your proposed structure of having 2 class libraries and 1 web project is one way to go for sure.
If you are going to do any kind of Dependency Injection using an Inversion of Control container, you might also want to consider having an "API" project for interfaces and an "Impl(ementation)" project for concrete classes that fulfill the interface contracts.
To echo danludwig, there really is no standard. I prefer breaking up libraries and namespaces according to functionality. Company.Db is my library for interacting with the database, Company.Mail are my wrappers around the Postmark mail service, etc.
I then tend to group like libraries into single repositories. So the 'storage' repository in source control holds Company.Db, Company.Caching, Company.FileStorage, etc. I have another repository 'messaging' that holds Company.Mail and Company.SMS (for interacting with Twilio to send text messages). When I branch out with new apps or new services (maybe a WCF endpoint for mobile clients), I can just pull down the 'messaging' repository, and I have all my class libraries for communicating with the user.
An application then looks like
Company.Application.Webite
\Libraries\Messaging
\Libraries\Messaging\Company.Mail
\Libraries\Storage
\Libraries\Messaging\Company.Db
\Libraries\Messaging\Company.Caching
\Libraries\Web
...
Company.Application.Wcf
\Libraries\Messaging
\Libraries\Storage
\Libraries\Messaging\Company.Db
\Libraries\Messaging\Company.Caching
...
This way, whether someone registers via the site, or via the mobile app, Company.Mail.MailServices.SendWelcomeEmail() sends the exact same welcome email, and there's no code duplication.
Whether this works for you, or even makes sense, who knows. I've also changed this scheme a hundred times, trying to find a layout that works with my development style/workflow. I wouldn't worry or stress too much about it, because whatever you pick, you're going to find things you like about it, and you'll find things you hate about it. I sometimes fall into the trap of spending more time trying to make everything "perfect", than to just code and change things I don't like.

ClickOnce: How do I pass a querystring value to my app *through the installer*?

My company currently builds separate MSI's for all of our clients, even though the app is 100% the same across the board (with a single exception, an ID in the app.config).
I would like to show them that we can publish in once place with ClickOnce, and simply add a query string parameter for each client's installer.
Example: http://mysite.com/setup.exe?ID=1234-56-7890
The issue that I'm having is that the above ("ID=1234...") is not being passed along to the "myapplication.application". What is happening instead is, the app is being installed successfully, and it is running the first time with an activation context, but the "ActivationUri" does not contain any query string values.
Is there a way to pass query string values FROM THE INSTALLER URL to the application's launch URL? If so, how?
After much searching (and discussing), the answer is simply that the current version of ClickOnce doesn't work that way. The installer does not pass the URL onto the application up it's first run.
Here is what I have done for a workaround (and it works great).
Change my setup package to have all of the required files uncompressed and loose (as apposed to using a CAB file, or embedding them in the installer).
Make an ASP.NET application (using Routing for URL handling) that listens for a request to "mysite.com/Installer/00123/Setup.exe"
Note: the route should listen for "/Installer/{ID}/*" where {ID} is 5 digits.
There is actually no directory called "00123", but rather, I'm using ASP.NET Routing to pickup those requests and then I map it to my actual directory that has the installer file in it.
I then hijack the request (parse the setup.exe to find the embedded URL that tells the installer program where to find the rest of the files... I then replace "/00000/" with the request URL that the user went to - in this case "00123".
As each file is being requested, I know which "version" of the file to send, because the ClickOnce Installer will be looking for "mysite.com/Installer/00123/SomeFile.dll" (or whatever).
Instead of using a 5-digit ID, you could use a GUID... it's up to you.
This solution works great for our organization... we currently have 37 clients who require unique customizations to their installer package, but we only have to actually build and publish ONE installer package and simply use the hijack method above.
At this point we have placeholders that we swap out so that it's easy to customize installers for as many clients as we want.
Example: in the app.config file we have displayName="{OrgName}" which is automatically replaced by one of the values in the database.
For me, "http://mysite.com/myapplication.application?id=1234-56-7890" seems to do the trick.
I know this is outdated, but I just wanted to provide the current solution.
To retrieve querystring parameters in a ClickOnce application:
Point the app/download/setup link to the application (with .application extension), not "setup.exe"
Add this function to your ClickOnce application to retrieve the querystring parameter collection:
private NameValueCollection GetQueryStringParameters()
{
NameValueCollection nameValueTable = new NameValueCollection();
if (ApplicationDeployment.IsNetworkDeployed)
{
string queryString = ApplicationDeployment.CurrentDeployment.ActivationUri.Query;
nameValueTable = HttpUtility.ParseQueryString(queryString);
}
return (nameValueTable);
}
Then to get a querystring param value:
var querystringParams = GetQueryStringParameters();
string param_value = querystringParams["param_name"];
Don't forget the Usings:
using System.Collections.Specialized;
using System.Deployment.Application;
using System.Web;
Source: https://learn.microsoft.com/en-us/visualstudio/deployment/how-to-retrieve-query-string-information-in-an-online-clickonce-application?view=vs-2019

Resources