Generate a swagger file from unknown objects (.net) - reflection

I want to develop a new API. It must connect a Dynamics CRM and front developers.
Today, the developed "workaround" is :
Fill an Excel file to describe the CRM and custom objects (with fetchXml, ...)
Write and concatenate strings to write a YAML file.
Copy this file to swagger editor and make tests with Postman
In a first step, I want to generate the schema without strings concatenations... (temporarily, waiting to replace the Excel file with OData and ASP.NET Core to have something more powerful)
For the moment, I use reflection to build my objects from the Excel file:
using System.Reflection;
using System.Reflection.Emit;
using Swashbuckle.Swagger;
...
var assemblyName = new AssemblyName(Guid.NewGuid().ToString();
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
var moduleBuilder = assemblyBuilder.DefineDynamicModule("Module");
var typeBuilder = moduleBuilder.DefineType(entity.CrmEntityName);
foreach (var attribute in entity.Attributes)
{
Type t = typeof(int);
switch (attribute.DataType.ToLower())
{
case "string":
t = typeof(string);
break;
case "integer":
t = typeof(int);
break;
case "date":
t = typeof(DateTime);
break;
}
typeBuilder.DefineField(attribute.CrmName, t.GetType(), FieldAttributes.Public);
var type = typeBuilder.CreateType();
// create the Swagger models here
}
How can I generate my swagger from these types and properties?
I've view an object named Schema which contains a name and another Schema, I don't know if is a wrong way...

You can implement and register a IDocumentFilter in which you can register the custom types that you've created with TypeBuilder.
public class MyDocFilter : Swashbuckle.Swagger.IDocumentFilter
{
public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, IApiExplorer apiExplorer)
{
schemaRegistry.GetOrRegister(MyDynamicType);
}
}
Make sure to register it in the SwaggerConfig like this:
c.DocumentFilter<MyDocFilter>();
The custom types should then appear in the "definitions" section of the Swagger document.
Now, if you have a generic OData GET method that appears multiple times in the Swagger document (one for each of the custom types) and you want each of those to display a proper Response type then you should also modify the paths of the SwaggerDocument something like:
public class MyDocFilter : Swashbuckle.Swagger.IDocumentFilter
{
public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, IApiExplorer apiExplorer)
{
foreach (string path in swaggerDoc.paths.Keys)
{
System.Type mytype = null; // Create custom type...
schemaRegistry.GetOrRegister(mytype);
var response = swaggerDoc.paths[path].get.responses["200"];
if (response.schema == null)
response.schema = new Schema();
response.schema.#ref = "#/definitions/" + mytype.Name;
}
}
}

Related

Unity to DryIoC conversion ParameterOverride

We are transitioning from Xamarin.Forms to .Net MAUI but our project uses Prism.Unity.Forms. We have a lot of code that basically uses the IContainer.Resolve() passing in a collection of ParameterOverrides with some primitives but some are interfaces/objects. The T we are resolving is usually a registered View which may or may not be the correct way of doing this but it's what I'm working with and we are doing it in backend code (sometimes a service). What is the correct way of doing this Unity thing in DryIoC? Note these parameters are being set at runtime and may only be part of the parameters a constructor takes in (some may be from already registered dependencies).
Example of the scenario:
//Called from service into custom resolver method
var parameterOverrides = new[]
{
new ParameterOverride("productID", 8675309),
new ParameterOverride("objectWithData", IObjectWithData)
};
//Custom resolver method example
var resolverOverrides = new List<ResolverOverride>();
foreach(var parameterOverride in parameterOverrides)
{
resolverOverrides.Add(parameterOverride);
}
return _container.Resolve<T>(resolverOverrides.ToArray());
You've found out why you don't use the container outside of the resolution root. I recommend not trying to replicate this error with another container but rather fixing it - use handcoded factories:
internal class SomeFactory : IProductViewFactory
{
public SomeFactory( IService dependency )
{
_dependency = dependency ?? throw new ArgumentNullException( nameof(dependency) );
}
#region IProductViewFactory
public IProductView Create( int productID, IObjectWithData objectWithData ) => new SomeProduct( productID, objectWithData, _dependency );
#endregion
#region private
private readonly IService _dependency;
#endregion
}
See this, too:
For dependencies that are independent of the instance you're creating, inject them into the factory and store them until needed.
For dependencies that are independent of the context of creation but need to be recreated for each created instance, inject factories into the factory and store them.
For dependencies that are dependent on the context of creation, pass them into the Create method of the factory.
Also, be aware of potential subtle differences in container behaviours: Unity's ResolverOverride works for the whole call to resolve, i.e. they override parameters of dependencies, too, whatever happens to match by name. This could very well be handled very differently by DryIOC.
First, I would agree with the #haukinger answer to rethink how do you pass the runtime information into the services. The most transparent and simple way in my opinion is by passing it via parameters into the consuming methods.
Second, here is a complete example in DryIoc to solve it head-on + the live code to play with.
using System;
using DryIoc;
public class Program
{
record ParameterOverride(string Name, object Value);
record Product(int productID);
public static void Main()
{
// get container somehow,
// if you don't have an access to it directly then you may resolve it from your service provider
IContainer c = new Container();
c.Register<Product>();
var parameterOverrides = new[]
{
new ParameterOverride("productID", 8675309),
new ParameterOverride("objectWithData", "blah"),
};
var parameterRules = Parameters.Of;
foreach (var po in parameterOverrides)
{
parameterRules = parameterRules.Details((_, x) => x.Name.Equals(po.Name) ? ServiceDetails.Of(po.Value) : null);
}
c = c.With(rules => rules.With(parameters: parameterRules));
var s = c.Resolve<Product>();
Console.WriteLine(s.productID);
}
}

How one can be notified that a variable goes out of scope in Workflow Foundation 4?

We are using Workflow Foundation 4 to implement custom logic in our application. One particular thing is that we are using variables of a custom type that are associated with a ressource in an external system.
When such a variable is no longer in use in a workflow, I would like to dispose of the corresponding resource in the external system.
How can my custom host be notified at runtime that my variable goes out of scope and/or is disposed. Do I need my variable objects to derive from a particular class or interface ? Do I need to inject a particular extension in the workflow instance ?
One way could be to implement a custom TrackingParticipant. This can be used to watch for when an activity's state changes to a closed state. When it is closed, you can inspect the arguments to see if any are of a resource that you'd like to clean up.
It could look something like this:
public interface IResource
{
}
public class MyTrackingParticipant : TrackingParticipant
{
private readonly MyResourceManager resourceManager;
public MyTrackingParticipant(MyResourceManager resourceManager)
{
this.resourceManager = resourceManager;
}
protected override void Track(TrackingRecord record, TimeSpan timeout)
{
var activityStateRecord = record as ActivityStateRecord;
if (activityStateRecord != null && activityStateRecord.State == ActivityStates.Closed)
{
// Scan arguments to see if resources should be deallocated from resource manager.
foreach (var keyValuePair in activityStateRecord.Arguments)
{
// If the argument is of a resource type...
var resource = keyValuePair.Value as IResource;
if (resource != null)
this.resourceManager.DeallocateResource(resource);
}
}
}
}
And using the custom tracking participant is just like any other WF extension:
var resourceManager = new MyResourceManager();
var wfResourceTrackingParticipant = new MyTrackingParticipant(resourceManager);
var workflow1 = new Workflow1();
var workflowApp = new WorkflowApplication(workflow1);
workflowApp.Extensions.Add(wfResourceTrackingParticipant);

ASP.NET VirtualPathProvider .Execute()': no suitable method found to override

I am currently trying to implement Razor Web Pages in older WebForms project, and also, make it possible to render partial views from string (taken from database elsewhere). I've implemented custom VirtualPathProvider with all overrides specified here: ASP.NET MVC and Virtual views and also overwritten these methods:
public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart)
{
if (virtualPath.Contains("RazorMigration.cshtml") && HttpContext.Current.Items.Contains("RazorTestingPage"))
{
return null;
}
return Previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
}
public override String GetFileHash(String virtualPath, IEnumerable virtualPathDependencies)
{
if (virtualPath.Contains("RazorMigration.cshtml") && HttpContext.Current.Items.Contains("RazorTestingPage"))
{
return Guid.NewGuid().ToString();
}
return Previous.GetFileHash(virtualPath, virtualPathDependencies);
}
Then when I am trying to actually render page (completeTemplate already contains pure HTML with razor already parsed) like this:
var rt = new RouteData();
rt.Values.Add("controller", "WebFormShimController");
var httpCtx = new HttpContextWrapper(System.Web.HttpContext.Current);
var ctx = new ControllerContext(new RequestContext(httpCtx, rt), new WebFormShimController());
try
{
HttpContext.Current.Items.Add("RazorTestingPage", completeTemplate);
IView view = ViewEngines.Engines.FindPartialView(ctx, System.IO.Path.GetFileName("RazorMigration")).View;
ViewContext vctx = new ViewContext(ctx, view,
new ViewDataDictionary { Model = model },
new TempDataDictionary(), httpCtx.Response.Output);
view.Render(vctx, System.Web.HttpContext.Current.Response.Output);
}
I always catch Exception On view.Render line saying this:
c:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\root\0e7dfb6a\c63cc9d1\App_Web_razormigration.cshtml.afd51fd3.jujzzimy.0.cs(41): error CS1009: Unrecognized escape sequence
I am not really sure what is the issue here, or where and how this path is constructed. If someone could point me in the right direction I would be very happy as I am trying to get it working for almost a week, but still no success.
EDIT: I found the bug - It was in my encoding when writing to file (so when file was written \NUL characters were added, which confused IIS)
Now I am getting this error:
CS0115: c:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\root\0e7dfb6a\c63cc9d1\App_Web_4b7b71f4-87c2-4364-be0e-19ec2a81ceccR.0.cs.Execute()': no suitable method found to override
I am sure I don't have error in my view as it only contains this:
<h1>Hello</h1>

Possible to manually apply OData parameters to result of `.AsQueryable()`?

I have a MVC4 WebAPI controller that returns an IQueryable, and therefore I can use $filter and friends in the URL to manipulate the result from the REST endpoint. Here's my controller:
public class EnrollmentController : ApiController
{
[Queryable]
public IQueryable<tblEnrollment> Get()
{
var context = new ProjectEntities();
context.ContextOptions.LazyLoadingEnabled = false;
return context.tblEnrollment.AsQueryable();
}
}
But, like this poster, I'm wanting to make the JSON output format a little different to be friendlier with Ember Data's expected format. So I'd like to return this instead:
return new { enrollment = context.tblEnrollment.AsQueryable() };
However, that breaks OData capability because I'm not returning the IQueryable to the WebAPI layer. So, I'm wondering if there's a way to do something like this:
return new { enrollment = context.tblEnrollment.AsQueryable().ApplyOData() };
Which I'm sure would be way to good to be true...but is there some way to explicitly process the OData parameters against an IQueryable instead of letting the WebAPI layer do it implicitly on the result set returned from a Get method? Or is there another way to accomplish what I want here?
Incidentally, I'm stuck on EF4 for the time being, because I can't upgrade to VS2012 (and hence to .NET4.5 and hence EF5). I could theoretically upgrade to EF 4.3.1, if it would help.
Instead of marking your action as [Queryable], you can add a parameter of type ODataQueryOptions and apply it manually. Here's what it might look like:
public class EnrollmentController : ApiController
{
public object Get(ODataQueryOptions<tblEnrollment> query)
{
var context = new ProjectEntities();
context.ContextOptions.LazyLoadingEnabled = false;
var queryResults = query.ApplyTo(context.tblEnrollment.AsQueryable());
return new { enrollment = queryResults };
}
}

ASP.NET - Avoid hardcoding paths

I'm looking for a best practice solution that aims to reduce the amount of URLs that are hard-coded in an ASP.NET application.
For example, when viewing a product details screen, performing an edit on these details, and then submitting the changes, the user is redirected back to the product listing screen. Instead of coding the following:
Response.Redirect("~/products/list.aspx?category=books");
I would like to have a solution in place that allows me to do something like this:
Pages.GotoProductList("books");
where Pages is a member of the common base class.
I'm just spit-balling here, and would love to hear any other way in which anyone has managed their application redirects.
EDIT
I ended up creating the following solution: I already had a common base class, to which I added a Pages enum (thanks Mark), with each item having a System.ComponentModel.DescriptionAttribute attribute containing the page's URL:
public enum Pages
{
[Description("~/secure/default.aspx")]
Landing,
[Description("~/secure/modelling/default.aspx")]
ModellingHome,
[Description("~/secure/reports/default.aspx")]
ReportsHome,
[Description("~/error.aspx")]
Error
}
Then I created a few overloaded methods to handle different scenarios. I used reflection to get the URL of the page through it's Description attribute, and I pass query-string parameters as an anonymous type (also using reflection to add each property as a query-string parameter):
private string GetEnumDescription(Enum value)
{
Type type = value.GetType();
string name = Enum.GetName(type, value);
if (name != null)
{
FieldInfo field = type.GetField(name);
if (field != null)
{
DescriptionAttribute attr = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
if (attr != null)
return attr.Description;
}
}
return null;
}
protected string GetPageUrl(Enums.Pages target, object variables)
{
var sb = new StringBuilder();
sb.Append(UrlHelper.ResolveUrl(Helper.GetEnumDescription(target)));
if (variables != null)
{
sb.Append("?");
var properties = (variables.GetType()).GetProperties();
foreach (var property in properties)
sb.Append(string.Format("{0}={1}&", property.Name, property.GetValue(variables, null)));
}
return sb.ToString();
}
protected void GotoPage(Enums.Pages target, object variables, bool useTransfer)
{
if(useTransfer)
HttpContext.Current.Server.Transfer(GetPageUrl(target, variables));
else
HttpContext.Current.Response.Redirect(GetPageUrl(target, variables));
}
A typical call would then look like so:
GotoPage(Enums.Pages.Landing, new {id = 12, category = "books"});
Comments?
I'd suggest that you derive your own class ("MyPageClass") from the Page class and include this method there:
public class MyPageClass : Page
{
private const string productListPagePath = "~/products/list.aspx?category=";
protected void GotoProductList(string category)
{
Response.Redirect(productListPagePath + category);
}
}
Then, in your codebehind, make sure that your page derives from this class:
public partial class Default : MyPageClass
{
...
}
within that, you can redirect just by using:
GotoProductList("Books");
Now, this is a bit limited as is since you'll undoubtedly have a variety of other pages like the ProductList page. You could give each one of them its own method in your page class but this is kind of grody and not smoothly extensible.
I solve a problem kind of like this by keeping a db table with a page name/file name mapping in it (I'm calling external, dynamically added HTML files, not ASPX files so my needs are a bit different but I think the principles apply). Your call would then use either a string or, better yet, an enum to redirect:
protected void GoToPage(PageTypeEnum pgType, string category)
{
//Get the enum-to-page mapping from a table or a dictionary object stored in the Application space on startup
Response.Redirect(GetPageString(pgType) + category); // *something* like this
}
From your page your call would be: GoToPage(enumProductList, "Books");
The nice thing is that the call is to a function defined in an ancestor class (no need to pass around or create manager objects) and the path is pretty obvious (intellisense will limit your ranges if you use an enum).
Good luck!
You have a wealth of options availible, and they all start with creating a mapping dictionary, whereas you can reference a keyword to a hard URL. Whether you chose to store it in a configuration file or database lookup table, your options are endless.
You have a huge number of options available here. Database table or XML file are probably the most commonly used examples.
// Please note i have not included any error handling code.
public class RoutingHelper
{
private NameValueCollecton routes;
private void LoadRoutes()
{
//Get your routes from db or config file
routes = /* what ever your source is*/
}
public void RedirectToSection(string section)
{
if(routes == null) LoadRoutes();
Response.Redirect(routes[section]);
}
}
This is just sample code, and it can be implemented any way you wish. The main question you need to think about is where you want to store the mappings. A simple xml file could do it:
`<mappings>
<map name="Books" value="/products.aspx/section=books"/>
...
</mappings>`
and then just load that into your routes collection.
public class BasePage : Page
{
public virtual string GetVirtualUrl()
{
throw new NotImplementedException();
}
public void PageRedirect<T>() where T : BasePage, new()
{
T page = new T();
Response.Redirect(page.GetVirtualUrl());
}
}
public partial class SomePage1 : BasePage
{
protected void Page_Load()
{
// Redirect to SomePage2.aspx
PageRedirect<SomePage2>();
}
}
public partial class SomePage2 : BasePage
{
public override string GetVirtualUrl()
{
return "~/Folder/SomePage2.aspx";
}
}

Resources