Expression Parameter is not displayed in generated Swagger documentation - asp.net-core

I have a Controller that has has a parameter of type Expression<Foo, bool> with the name query however that parameter does not show up in the generated swagger.json file. Instead a lot (>1000) parameters that have names like these show up:
Body.CanReduce
ReturnType.IsGenericMethodParameter
Type.IsGenericType
I would like to tell SwaggerGen to have my parameter just shown up as a string. If it is possible using a Filter, that would be my preferred way, but an Attribute would be fine too.
I already tried using an IOperationFilter, but it did not work as operation.Parameters does not even seem to have a paramater with the name query.
private static readonly Type _expressionType = typeof(Expression);
public void Apply(Operation operation, OperationFilterContext context)
{
foreach (var parameter in context.ApiDescription.ActionDescriptor.Parameters)
{
if(_expressionType.IsAssignableFrom(parameter.ParameterType))
{
// The parameter is found ...
var expressionParameter = operation.Parameters.FirstOrDefault(p => p.Name == parameter.Name);
if (expressionParameter != null)
Debugger.Break(); // ... but is not in the operation.Parameters collection although the >1000 mentioned above are.
}
}
}
P.S. to anyone interested: I'm using a custom ModelBinder and System.Linq.Dynamic to parse a query string to an Expression<Foo, bool>

Related

Executing a Remote.Linq expression against data

I'm using Remote.Linq to serialise / deserialise my Expressions as I want to create the ability to send dynamic expressions from a client application to our web services. Standard .NET expressions cannot be serialised so I'm using Remote.Linq instead.
However, I cannot see how to execute the Expression. Normally I would invoke the Compile() and Invoke() methods to execute the Expression against the data. But Remote.Linq expressions don't support such methods.
The following unit test may explain more clearly what I'm trying to achieve.
[TestMethod]
public void SerializeLinqExpressionsTests()
{
var testdata = GetTestdata();
Expression<Func<ModuleEntityAdmins, ModuleEntityAdmin>> expr1 = m => m.Modules.Find(q => q.Id == 1);
var remoteExpression1 = expr1.ToRemoteLinqExpression();
string strexpr1 = SerialiseExpression(remoteExpression1);
try
{
var deserexpr1 = DeserialiseExpression<Remote.Linq.Expressions.LambdaExpression>(strexpr1.NormalizeJsonString());
//what is the equivalent of doing this with a Remote.Linq Expression?
var compiled1 = expr1.Compile();
var result = compiled1.Invoke(testdata);
Assert.IsNotNull(result);
Assert.IsTrue(result.Id == 1);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Assert.Fail("Error deserialising LINQ expression tree");
}
}
How do you invoke a Remote.Linq expression?
A remote linq expression may be converted back into a system linq expression and be compiled and executed as such.
However, this is not what you actually want when sending expressions to a server to query data. On server side you want to use the Execute extension methods to execute your expression against a data source. Make sure to add a using for namespace Remote.Linq.Expressions.
Here's the sample code from Remote.Linq source repo:
using Remote.Linq.Expressions;
public interface IQueryService
{
IEnumerable<DynamicObject> ExecuteQuery(Expression queryExpression);
}
public class QueryService : IQueryService, IDisposable
{
// any linq provider e.g. entity framework, nhibernate, ...
private IDataProvider _datastore = new ObjectRelationalMapper();
// you need to be able to retrieve an IQueryable by type
private Func<Type, IQueryable> _queryableProvider = type => _datastore.GetQueryableByType(type);
public IEnumerable<DynamicObject> ExecuteQuery(Expression queryExpression)
{
// `Execute` is an extension method provided by Remote.Linq
// it applies an expression to a data source and returns the result
return queryExpression.Execute(queryableProvider: _queryableProvider);
}
public void Dispose() => _datastore.Dispose();
}
Also, there are additional nuget packages for expression execution with EF and EF Core, so you can simply provide a DbContext to the Execute method.
In addition you may want to check out the demos/samples found in the project's github repo.

Modify view path from MVC controller before rendering it

In my MVC 4 Controller, I want to override the View() method
ViewResult View(string viewName, string masterName, object model) {}
So that I can manipulate the view being rendered by the action method. To do this, I want to be able to obtain the physical path of the view file. I have tried the following:
string viewName = this.ControllerContext.RouteData.Route
.GetVirtualPath(this.ControllerContext.RequestContext, null)
.VirtualPath;
For example, this might return "/Errors/MissingParameters" when what I really want it to return is something like:
"~/Views/Errors/MissingParameters"
or, even better:
"~/Views/Errors/MissingParameters.cshtml"
Just to add complication, I also need it to cope with Areas, so if I had the same example running in an Area named "Surveys", I would want it to return something like:
"~/Areas/Surveys/Views/Errors/MissingParameters"
The reason I want to do this is that I'm experimenting with using views for globalization, so I might have two views:
"~/Views/Errors/MissingParameters.cshtml" // default view (en-GB)
"~/Views/Errors/MissingParameters_de-DE.cshtml" // German view (de-DE)
and I want to be able to check if the view exists for the current language/culture before referencing it.
Any advice would be much appreciated.
Thanks.
EDIT: This part will not work or is hard to implement
You'd rather use an action filter which will let you manipulate the Result before executing it.
Particularly you need a Result filter. Implement the IResultFilter.onResultExecuting method, and change the result there. Particularly when you implement this method:
void OnResultExecuting(ResultExecutingContext filterContext)
You can access the ResultExecutingContext.Result Property. This property will contain your view. If you cast it to System.Web.Mvc.ViewResultBase you'll have access to the ViewName and you'll be able to change it.
If you've never implemented a filter, this is a good hands-on-lab on the subject. In this case it implements another kind of filter, but it's just the same.
As an answer to the OP comment, it's perfectly normal that ViewName is missing, and View is still null. ViewName wouldn't be empty only if the case that the view is returned with name, like this: return View("Index");. And, the ViewName would be just, not the whole path to the view. So this is not a solution. So, to have this solution working you would have to deal with route data, controller context, etc. to find the view. (More on this below.)
EDIT: Solution, register a custom view engine
When MVC has to render a view it gets the information from the route data, the controller context, the view name (that, as explained above can be empty), and the conventions that apply.
Particularly, in MVC there is a collection of registered view engines which are required to find the view calling there FindView() method. The view engine will return a ViewEngineResult which has the found view, if one was found, or a list of the paths where the view has been unsuccesfully sought.
So, to modify the template path, you can override this funcionality: let the original class find the view, and, if it is found, modify the path.
To do show you need to take theses steps:
Inherit the view engine which you're using (my sampel code inherits Razor view engine)
Register your vie engine, so that it's queried before the original view engine (in my sample code I simply clear the registered engines list, and register mine. The original list includes razor and web form view engines)
This is the code for the inherited view engine:
public class CustomRazorViewEngine : FixedRazorViewEngine
{
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
ViewEngineResult result
= base.FindView(controllerContext, viewName, masterName, useCache);
if (result.View != null)
{
// Modify here !!
}
return result;
}
public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
{
ViewEngineResult result
= base.FindPartialView(controllerContext, partialViewName, useCache);
if (result.View != null)
{
// Modify here !!
}
return result;
}
static readonly PropertyInfo ViewPathProp
= typeof(RazorView).GetProperty("ViewPath");
public void SetViewPath(RazorView view, string path)
{
ViewPathProp.SetValue(view, path);
}
}
NOTE 1: where you read // Modify here !! you can modify the path property of the result.View. Cast it to RazorView: (result.View as RazorView).ViewPath. As the ViewPath setter is protected, you need to set it using Reflection: you can use the SetViewPath method for this.
NOTE 2: As you can see I'm not inheriting the RazorViewEngine but the FixedRazorViewEngine. If you loook for this class in MSDN you'll get not results, but if you look the original content of the registered view engines list, you'll find this class. I think this depends on an installed package in the project, and I think it solves a bug in MVC4. If you don't finf it in Microsoft.Web.Mvc namespace, inherit the original RazorViewEngined
NOTE 3: after the view is found, the view engine executes it, using the ViewEngineResult, so, if you change it, it will be executed with the new view path
And finally, you need to change the list of registered engines, in global.asax application start event, like this:
protected void Application_Start()
{
// Original content:
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
// Added content:
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new CustomRazorViewEngine());
}
NOTE: it would be cleaner, if you created a ViewEngineConfig class in App_Start folder, and invoked an static method of this class, just as it's done with all other configurations.
Answer was copied from here.
Well if you don't mind having your code tied to the specific view engine you're using, you can look at the ViewContext.View property and cast it to WebFormView
var viewPath = ((WebFormView)ViewContext.View).ViewPath;
I believe that will get you the view name at the end.
EDIT: Haacked is absolutely spot-on; to make things a bit neater I've wrapped the logic up in an extension method like so:
public static class IViewExtensions {
public static string GetWebFormViewName(this IView view) {
if (view is WebFormView) {
string viewUrl = ((WebFormView)view).ViewPath;
string viewFileName = viewUrl.Substring(viewUrl.LastIndexOf('/'));
string viewFileNameWithoutExtension = Path.GetFileNameWithoutExtension(viewFileName);
return (viewFileNameWithoutExtension);
} else {
throw (new InvalidOperationException("This view is not a WebFormView"));
}
}
}
which seems to do exactly what I was after.
Another solution here
((System.Web.Mvc.RazorView)htmlHelper.ViewContext.View).ViewPath
net-mvc
i am registering the area but i don't want my
url: "{area}/{controller}/{action}/{id}",
instead i want it to be like
url: "{controller}/{action}/{id}",
so i have registered my area like
context.MapRoute(
name: "AreaName_default",
url: "{controller}/{action}/{id}",
namespaces: new[] { "SolutionName.AreaName.Controllers" }
);
and i don't want to add the hard code string viewpath while returning view in every action method like
return View("~/Areas/AreaName/Views/ControllerName/ViewName.cshtml", model);
so i have created one result filter and override OnResultExecuting function
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
string areaName = AreaNameAreaRegistration.PropoertyName;
if (filterContext.Result.GetType() == typeof(ViewResult) || filterContext.Result.GetType() == typeof(PartialViewResult))
{
dynamic viewResult = filterContext.Result;
string viewname = string.IsNullOrEmpty(viewResult.ViewName) ? Convert.ToString(filterContext.RouteData.Values["action"]) : viewResult.ViewName;
string folder = Convert.ToString(filterContext.RouteData.Values["controller"]);
string lateralHireAreaViewPath = $"~/Areas/{areaName}/Views/";
string extension = viewname.Contains(".cshtml") ? "" : ".cshtml";
viewResult.ViewName = string.Concat(lateralHireAreaViewPath, folder, "/", viewname, extension);
ViewEngineResult result = ViewEngines.Engines.FindView(filterContext.Controller.ControllerContext, viewResult.ViewName, null);
if (result.View == null)
{
//searched in shared folder
lateralHireAreaViewPath = string.Concat(lateralHireAreaViewPath, "Shared/");
viewResult.ViewName = string.Concat(lateralHireAreaViewPath, "/", viewname, extension);
}
}
}

Can you use RequestFactory's .with() method with named queries?

I'm trying to make a call to a database using RequestFactory with Hibernate/JPA, and I want to retrieve a list of entities with embedded entities returned as well. I know that the .with() method works for methods like .find(), but it doesn't seem to work with custom queries.
The current way I'm doing it is as follows:
I used a named query in the entity class for the query. (Primary Entity is Name, embedded entity is a Suffix entity called nameSuffix)
#NamedQueries({ #NamedQuery(name = "Query.name", query = "select * from NameTable") })
Then in the service class, the .list() method, which is what I'd like to call with RequestFactory, is as follows.
public List<Name> list() {
return emp.get().createNamedQuery("Query.name").getResultList();
}
Finally, this is how I make the call in my client side code:
NameRequest context = requestFactory.createNameRequest();
context.list().with("nameSuffix").fire(new Receiver<List<NameProxy>>(){
public void onSuccess(List<NameProxy> response) {
String suff = response.get(0).getNameSuffix().getText();
}
});
In the above code, it says that getNameSuffix() returns null, which would imply that .with("nameSuffix") does not work with the .list() call like it does with the standard .find() method.
Is there a way to build a call that would return a list of entities and their embedded entities using .with(), or do I need to do it another way? If I need to do it another way, has anyone figured out a good way of doing it?
I think you misunderstood what the method with() is thought for, unless you had a method getNameSuffix which returns the NameSuffixentity. This is what the documentation says about it:
When querying the server, RequestFactory does not automatically populate relations in the object graph. To do this, use the with() method on a request and specify the related property name as a String
So, what you have to pass to the method is a list of the name of the child entities you want to retrieve. I hope this example could be helpful:
class A {
String getS(){return "s-a"}
B getB(){return new B();}
}
class B {
String getS(){return "s-b";}
C getC(){return new C();}
}
class C {
String getS(){return "s-c";}
}
context.getA().fire(new Receiver<A>(){
public void onSuccess(A response) {
// return 's-a'
response.getS();
// trhows a NPE
response.getB().getS();
}
});
context.getA().with("b").fire(new Receiver<A>(){
public void onSuccess(A response) {
// return 's-a'
response.getS();
// return 's-b'
response.getB().getS();
// trhows a NPE
response.getB().getC().getS();
}
});
context.getA().with("b.c").fire(new Receiver<A>(){
public void onSuccess(A response) {
// return 's-a'
response.getS();
// return 's-b'
response.getB().getS();
// return 's-c'
response.getB().getC().getS();
}
});

The underlying connection was closed error while using .Include on EF objects

Following line of code gives me an error saying "The underlying connection was closed".
return this.repository.GetQuery<Countries>().Include(g => g.Cities).AsEnumerable().ToList();
But if I remove .Include(g => g.cities) it works fine.
this code is written in one of the operation in my WCF service, and I try to test it using WCF test client. I tried by calling this operation from MVC application also, and the same issue was occurring there too.
Also, i am using generic repository with entity framework
Repository code (only few important extract)
Constructor:
public GenericRepository(DbContext objectContext)
{
if (objectContext == null)
throw new ArgumentNullException("objectContext");
this._dbContext = objectContext;
this._dbContext.Configuration.LazyLoadingEnabled = false;
this._dbContext.Configuration.ProxyCreationEnabled = false;
}
GetQuery method:
public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class
{
var entityName = GetEntityName<TEntity>();
return ((IObjectContextAdapter)DbContext).ObjectContext.CreateQuery<TEntity>(entityName);
}
Attempt#1
Created following overloads in repository code:
public IQueryable<TEntity> GetQuery<TEntity>(params string[] includes) where TEntity : class
{
var entityName = GetEntityName<TEntity>();
IQueryable<TEntity> query = ((IObjectContextAdapter)DbContext).ObjectContext.CreateQuery<TEntity>(entityName);
foreach(string include in includes)
{
query = query.Include(include);
}
return query;
}
public IQueryable<TEntity> GetQuery<TEntity>(Expression<Func<TEntity, bool>> predicate, params string[] includes) where TEntity : class
{
return GetQuery<TEntity>(includes).Where(predicate);
}
WCF is now trying to execute following line of code:
return this.repository.GetQuery<Countries>("Cities").AsEnumerable().ToList()
But it still gives the same error of "The underlying connection was closed". I tested it in WCF test client. However, when I debug the repository code it shows the navigation object getting included in result, but the issue seems occurring while trying to pass the output to client (WCF test client, or any other client)
After looking at the code you've now posted, I can conclude that, indeed, your DbContext is being closed at the end of the GetQuery method, and is thus failing when you try to use include. What you might want to do to solve it is to have an optional params variable for the GetQuery method that will take in some properties to be included, and just do the include right in the GetQuery method itself.

Can I stop my WCF generating ArrayOfString instead of string[] or List<string>

I am having a minor problem with WCF service proxies where the message contains List<string> as a parameter.
I am using the 'Add Service reference' in Visual Studio to generate a reference to my service.
// portion of my web service message
public List<SubscribeInfo> Subscribe { get; set; }
public List<string> Unsubscribe { get; set; }
These are the generated properties on my MsgIn for one of my web methods.
You can see it used ArrayOfString when I am using List<string>, and the other takes List<SubscribeInfo> - which matches my original C# object above.
[System.Runtime.Serialization.DataMemberAttribute(EmitDefaultValue=false)]
public System.Collections.Generic.List<DataAccess.MailingListWSReference.SubscribeInfo> Subscribe {
get {
return this.SubscribeField;
}
set {
if ((object.ReferenceEquals(this.SubscribeField, value) != true)) {
this.SubscribeField = value;
this.RaisePropertyChanged("Subscribe");
}
}
}
[System.Runtime.Serialization.DataMemberAttribute(EmitDefaultValue=false)]
publicDataAccess.MailingListWSReference.ArrayOfString Unsubscribe {
get {
return this.UnsubscribeField;
}
set {
if ((object.ReferenceEquals(this.UnsubscribeField, value) != true)) {
this.UnsubscribeField = value;
this.RaisePropertyChanged("Unsubscribe");
}
}
}
The ArrayOfString class generated looks like this. This is a class generated in my code - its not a .NET class. It actually generated me a class that inherits from List, but didn't have the 'decency' to create me any constructors.
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")]
[System.Runtime.Serialization.CollectionDataContractAttribute(Name="ArrayOfString", Namespace="http://www.example.com/", ItemName="string")]
[System.SerializableAttribute()]
public class ArrayOfString : System.Collections.Generic.List<string> {
}
The problem is that I often create my message like this :
client.UpdateMailingList(new UpdateMailingListMsgIn()
{
Email = model.Email,
Name = model.Name,
Source = Request.Url.ToString(),
Subscribe = subscribeTo.ToList(),
Unsubscribe = unsubscribeFrom.ToList()
});
I really like the clean look this gives me.
Now for the actual problem :
I cant assign a List<string> to the Unsubscribe property which is an ArrayOfString - even though it inherits from List. In fact I cant seem to find ANY way to assign it without extra statements.
I've tried the following :
new ArrayOfString(unsubscribeFrom.ToList()) - this constructor doesn't exist :-(
changing the type of the array used by the code generator - doesn't work - it always gives me ArrayOfString (!?)
try to cast List<string> to ArrayOfString - fails with 'unable to cast', even though it compiles just fine
create new ArrayOfString() and then AddRange(unsubscribeFrom.ToList()) - works, but I cant do it all in one statement
create a conversion function ToArrayOfString(List<string>), which works but isn't as clean as I want.
Its only doing this for string, which is annoying.
Am i missing something? Is there a way to tell it not to generate ArrayOfString - or some other trick to assign it ?
Any .NET object that implements a method named "Add" can be initialized just like arrays or dictionaries.
As ArrayOfString does implement an "Add" method, you can initialize it like this:
var a = new ArrayOfString { "string one", "string two" };
But, if you really want to initialize it based on another collection, you can write a extension method for that:
public static class U
{
public static T To<T>(this IEnumerable<string> strings)
where T : IList<string>, new()
{
var newList = new T();
foreach (var s in strings)
newList.Add(s);
return newList;
}
}
Usage:
client.UpdateMailingList(new UpdateMailingListMsgIn()
{
Email = model.Email,
Name = model.Name,
Source = Request.Url.ToString(),
Subscribe = subscribeTo.ToList(),
Unsubscribe = unsubscribeFrom.To<ArrayOfString>()
});
I prefer not to return generic types across a service boundary in the first place. Instead return Unsubscribe as a string[], and SubscriptionInfo as SubscriptionInfo[]. If necessary, an array can easily be converted to a generic list on the client, as follows:
Unsubscribe = new List<string>(unsubscribeFrom);
Subscribe = new List<SubscriptionInfo>(subscribeTo);
Too late but can help people in the future...
Use the svcutil and explicitly inform the command line util that you want the proxy class to be serialized by the XmlSerializer and not the DataContractSerializer (default). Here's the sample:
svcutil /out:c:\Path\Proxy.cs /config:c:\Path\Proxy.config /async /serializer:XmlSerializer /namespace:*,YourNamespace http://www.domain.com/service/serviceURL.asmx
Note that the web service is an ASP.NET web service ok?!
If you are using VS 2008 to consume service then there is an easy solution.
Click on the "Advanced..." button on the proxy dialog that is displayed when you add a Service Reference. In the Collection Type drop down you can select System.Generic.List. The methods returning List should now work properly.
(Hope this is what you were asking for, I'm a little tired and the question was a tad difficult for me to read.)