ASP.NET MVC Structure/Logic - vb.net

I have a few questions about where the logic goes in a MVC app.
This is a sample action in a controller and was wondering if this is too much logic, and if so, where else would you put it:
FYI - Manager is sort of a service type layer where we transform BO to DTO/ViewModels back and forth to another layer that does our BL
Public Function ChangeClaim(model As ChangeClaimViewModel) As ActionResult
Manager.SetClaimNumber(model.ClaimNumber)
Dim securityToken = Manager.ClaimSecurityToken
If (securityToken.ValidClaim) Then
Session("ClaimNumber") = model.ClaimNumber
If (Not securityToken.ConflictAccess) Then
ModelState.AddModelError("ClaimNumber", "You do not have access to this claim.")
End If
Else
ModelState.AddModelError("ClaimNumber", "Invalid claim number.")
End If
If (Not ModelState.IsValid) Then
Return View(Manager.GetViewModel())
End If
If (model.URL.Contains("ChangeClaim") OrElse model.URL.Contains("EnterClaim")) Then
model.URL = Url.Action("Index", "Home")
End If
Return Redirect(model.URL)
End Function
Also, I would assume that hanging bool of a ViewModel to use to do logic like so in a view is OK?
#if (Model.HasExposureAccess)
{
<li>#Model.Labels.Reimbursements</li>
}
Any other suggestions for improvement?
Oh and sorry for the mix of VB & C#, the shop I work in does everything except the views in VB and I had to fight to get to do the views in C#!!??
EDIT #1
So as for the If (securityToken.ValidClaim) Then let me just try my best to run you through the process and see if you have any suggestions.
When a user tries to change the claim number, it comes in on the model, it is passed off to the Manager.SetClaim method (the managers are a sort of service layer we created to be able to use our existing BO framework which, don't laugh, is a heavily modified version of CSLA.NET V1, they are very tightly coupled objects as far as the BL and DAL all live inside the objects. Not my choice at all, but what do you do :)) which validates that it is a valid claim and the user has access to it. I am trying my hardest to keep the layers separated as best possible:
1) MVC App
2) Application Managers
3) Existing BOF
Did I make any sense?
Edit #2
So I have put the logic from the controller action into an action filter like so:
Public Class ValidateClaimAttribute
Inherits ActionFilterAttribute
Public Overrides Sub OnActionExecuting(filterContext As System.Web.Mvc.ActionExecutingContext)
MyBase.OnActionExecuting(filterContext)
Dim model As ChangeClaimViewModel = CType(filterContext.ActionParameters("model"), ChangeClaimViewModel)
Dim manager As IInjuredWorkerManager = DependencyResolver.Current.GetService(Of IInjuredWorkerManager)()
Dim securityToken = manager.ClaimSecurityToken
manager.SetClaimNumber(model.ClaimNumber)
If (securityToken.ValidClaim) Then
filterContext.HttpContext.Session("ClaimNumber") = model.ClaimNumber
If (Not securityToken.ConflictAccess) Then
filterContext.Controller.ViewData.ModelState.AddModelError("ClaimNumber", "You do not have access to this claim.")
End If
Else
filterContext.Controller.ViewData.ModelState.AddModelError("ClaimNumber", "Invalid claim number.")
End If
End Sub
End Class
<ValidateClaim()>
Public Function ChangeClaim(model As ChangeClaimViewModel) As ActionResult
If (Not ModelState.IsValid) Then
Return View(Manager.GetViewModel())
End If
If (model.URL.Contains("ChangeClaim") OrElse model.URL.Contains("EnterClaim")) Then
model.URL = Url.Action("Index", "Home")
End If
Return Redirect(model.URL)
End Function
Does this look like more of the correct way of doing this?
Edit #3
So, I have further optimized to this:
<ValidateClaim()>
Public Function ChangeClaim(model As ChangeClaimViewModel) As ActionResult
If (Not ModelState.IsValid) Then
Return View(Manager.GetViewModel())
End If
Return New MyRedirect(model.URL)
End Function
Public Class ValidateClaimAttribute
Inherits ActionFilterAttribute
Public Overrides Sub OnActionExecuting(filterContext As System.Web.Mvc.ActionExecutingContext)
MyBase.OnActionExecuting(filterContext)
Dim model As ChangeClaimViewModel = CType(filterContext.ActionParameters("model"), ChangeClaimViewModel)
Dim manager As IInjuredWorkerManager = DependencyResolver.Current.GetService(Of IInjuredWorkerManager)()
Dim securityToken = manager.ClaimSecurityToken
manager.SetClaimNumber(model.ClaimNumber)
If (securityToken.ValidClaim) Then
filterContext.HttpContext.Session("ClaimNumber") = model.ClaimNumber
If (Not securityToken.ConflictAccess) Then
filterContext.Controller.ViewData.ModelState.AddModelError("ClaimNumber", "You do not have access to this claim.")
End If
Else
filterContext.Controller.ViewData.ModelState.AddModelError("ClaimNumber", "Invalid claim number.")
End If
End Sub
End Class
Public Class MyRedirect
Inherits ActionResult
Private _url As String
Public Sub New(url As String)
_url = url
End Sub
Public Overrides Sub ExecuteResult(context As System.Web.Mvc.ControllerContext)
Dim urlHelper As New UrlHelper(context.RequestContext)
If (_url.Contains("ChangeClaim") OrElse _url.Contains("EnterClaim")) Then
_url = urlHelper.Action("Index", "Home")
End If
context.HttpContext.Response.Redirect(_url)
End Sub
End Class
Full Controller Code:
Imports System.Web.Mvc
Imports System.Security.Principal
Imports Telerik.Web.Mvc
Imports System.Globalization
Namespace Controllers
<HandleException(View:="Error")>
<OutputCache(Duration:=0)>
Public MustInherit Class InjuredWorkerController
Inherits SAIF.Web.Mvc.Framework.Controllers.ContextController
Public Property Manager As IInjuredWorkerManager
Public Sub New(manager As IInjuredWorkerManager)
_Manager = manager
_Manager.ValidationDictonary = New ModelStateWrapper(ModelState)
End Sub
<ValidateClaim()>
Public Function ChangeClaim(model As ChangeClaimViewModel) As ActionResult
If (Not ModelState.IsValid) Then
Return View(Manager.GetViewModel())
End If
Return New MyRedirect(model.URL)
End Function
Public Function SetCulture(culture As String, returnUrl As String) As ActionResult
Response.Cookies.Add(New HttpCookie("culture") With {
.Value = culture,
.Expires = DateTime.Now.AddYears(1)
})
Return Redirect(returnUrl)
End Function
Protected Overrides Sub ExecuteCore()
Dim cultureName = "en-US"
Dim cultureCookie = Request.Cookies("culture")
If (cultureCookie IsNot Nothing) Then
cultureName = Request.Cookies("culture").Value
End If
Threading.Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureName)
Threading.Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(cultureName)
MyBase.ExecuteCore()
End Sub
Protected Overrides Sub OnActionExecuting(filterContext As System.Web.Mvc.ActionExecutingContext)
MyBase.OnActionExecuting(filterContext)
Dim controller = filterContext.RouteData.Values("controller").ToString
Dim action = filterContext.RouteData.Values("action").ToString
If (action.ToLower = "enterclaim" OrElse action.ToLower = "changeclaim") Then
Return
Else
Dim claimNumber As String = String.Empty
Dim workerID As Decimal
If (Session("ClaimNumber") IsNot Nothing) Then
claimNumber = Session("ClaimNumber").ToString
End If
If (Session("WorkerID") IsNot Nothing) Then
workerID = CDec(Session("WorkerID"))
End If
If (String.IsNullOrEmpty(claimNumber)) Then
If (workerID = 0) Then
If (Manager.IsExternalUser) Then
workerID = Manager.GetWorkerIdByDomainUser
claimNumber = Manager.GetMostRecentClaimNumber(workerID)
Else
filterContext.Result = New RedirectResult("/MyClaim/Home/EnterClaim")
End If
End If
End If
Manager.SetClaimNumber(claimNumber)
End If
End Sub
Public Function SendMessage(<Bind(prefix:="SendMessage")> model As IWSendMessageViewModel) As ActionResult
Manager.SendAdjusterEmail(model.AdjusterEmail, model.PersonEmail, "IW Contact Message", model.Message, model.SendCopyToSender)
Return Json(New With {.message = "Success"}, "application/json")
End Function
End Class
End Namespace

That's definitely way too much for a controller action. Things like If (Not securityToken.ConflictAccess) should go into a custom Authorize attribute that your controller action should be decorated with. Things like ModelState.AddModelError("ClaimNumber", "Invalid claim number.") go into a custom validation attribute, or if you are using FluentValidation.NET (which I very strongly recommend you to) in a FV validator for your model.
Things like this:
If (model.URL.Contains("ChangeClaim") OrElse model.URL.Contains("EnterClaim")) Then
model.URL = Url.Action("Index", "Home")
End If
could go into a custom action result that you will return.
As far as your conditional logic in the view is concerned, it is fine. You could use your view model properties to show/hide certain UI elements. Obviously proper authorization (via custom Authorize attributes) should be done in the corresponding controller actions, because the fact that you have hidden a link from the user doesn't mean that a malicious user cannot forge a request and still be able to invoke your controller action.

Related

Error 'Value cannot be null:nodeName' when creating new iteration

I'm getting an error when attempting to create a new Iteration using the Client SDK:
Value cannot be null.
Parameter name: nodeName
As a test, I tried creating it using Postman and the REST API—as suggested here—and it succeeded.
I've been using this successfully for quite a while now to stub out my sprint hierarchy for the year. This is the first such occurrence of this error—past year runs have gone off without a hitch. I haven't changed anything (that I know of) since last year's successful run.
As we can see, the iteration's Name property is being properly set. I tried Overloads instead of Shadows, but that didn't help.
How can I troubleshoot this to find out what nodeName is and how to populate it using the Client SDK?
Here's my code:
Module Main()
Private Sub AddYear(Year As Integer, Client As WorkItemTrackingHttpClient)
Dim oIterationYear As Classifications.Iteration
Dim dFinishDate As Date
Dim dStartDate As Date
Console.WriteLine($"Year:{vbTab}{vbTab}{Year}")
dFinishDate = New Date(Year, 12, 31)
dStartDate = New Date(Year, 1, 1)
oIterationYear = New Classifications.Iteration(Client, TeamProject, Year, dStartDate, dFinishDate)
oIterationYear.Save()
...
End Sub
End Module
Public Class Iteration
Inherits Base
Public Sub New(Client As WorkItemTrackingHttpClient, TeamProject As TeamProjects, Name As String, StartDate As Date, FinishDate As Date)
Me.New(Client, TeamProject, Name, StartDate, FinishDate, Nothing)
End Sub
Public Sub New(Client As WorkItemTrackingHttpClient, TeamProject As TeamProjects, Name As String, StartDate As Date, FinishDate As Date, Parent As Iteration)
MyBase.New(Client, TeamProject, Parent)
Me.StructureType = TreeNodeStructureType.Iteration
Me.FinishDate = FinishDate
Me.StartDate = StartDate
Me.Name = Name
End Sub
...
End Class
Public MustInherit Class Base
Inherits WorkItemClassificationNode
Public Sub New(Client As WorkItemTrackingHttpClient, TeamProject As TeamProjects, Parent As Base)
Me.ProjectName = TeamProject.ToDescription
Me.Parent = Parent
Me.Client = Client
End Sub
Public Sub Save()
If Me.Parent.IsNothing Then
Me.Node = Me.Client.CreateOrUpdateClassificationNodeAsync(Me, Me.ProjectName, Me.StructureType).Result <-- Error
Else
Me.Node = Me.Client.CreateOrUpdateClassificationNodeAsync(Me, Me.ProjectName, Me.StructureType, path:=Me.Path).Result
End If
End Sub
...
Public Shadows Property Name As String
Get
If Me.Node.IsNothing Then
Name = Me._Name
Else
Name = Me.Node.Name
End If
End Get
Set(Value As String)
Me._Name = Value
End Set
End Property
Private _Name As String
End Class
Note: this is a language-agnostic question, thus I've intentionally omitted the VB.NET tag. An answer can come in either VB.NET or C#—I'm fine with either one.
-- EDIT --
Based on the design suggestions found in the accepted answer, I've come up with this solution that works:
Public MustInherit Class Base
Public Sub New(Client As WorkItemTrackingHttpClient, TeamProject As TeamProjects, Parent As Base)
Me.Node = New WorkItemClassificationNode With {
.StructureType = StructureType,
.Name = Name
}
Me.ProjectName = TeamProject.ToDescription
Me.Parent = Parent
Me.Client = Client
Me.Name = Name
End Sub
Public Sub Save()
If Me.Parent.IsNothing Then
Me.Node = Me.Client.CreateOrUpdateClassificationNodeAsync(Me.Node, Me.ProjectName, Me.StructureType).Result
Else
Me.Node = Me.Client.CreateOrUpdateClassificationNodeAsync(Me.Node, Me.ProjectName, Me.StructureType, path:=Me.Path).Result
End If
End Sub
...
Public Property Name As String
Get
Return Me.Node.Name
End Get
Private Set(Value As String)
Me.Node.Name = Value
End Set
End Property
End Class
Essentially all I did was remove the base class' inheritance from WorkItemClassificationNode and store a node reference internally in all cases. I also simplified the Name property implementation.
As for why it suddenly stopped working with no change in my code, the only thing I can think of is the remote possibility that there was a change in the compiler that affected how the SDK evaluates the Shadows and Overloads keywords. That's a long shot, I know, but I'm at a complete loss otherwise.
Bottom line, it works now.
I can create new iteration using Microsoft.TeamFoundation.WorkItemTracking.WebApi in Azure DevOps Services .NET SDK.
Please check out below example:
class Program
{
static void Main(string[] args)
{
Uri accountUri = new Uri("https://dev.azure.com/org/");
string personalAccessToken = "pat";
VssConnection _connection = new VssConnection(accountUri, new VssBasicCredential(string.Empty, personalAccessToken));
WorkItemTrackingHttpClient workItemTrackingHttpClient = _connection.GetClient<WorkItemTrackingHttpClient>();
Iteration iteration = new Iteration(workItemTrackingHttpClient,2021, "projectName");
iteration.SaveNode();
}
}
public class Iteration
{
WorkItemTrackingHttpClient client;
WorkItemClassificationNode node;
string project;
string path;
public Iteration(WorkItemTrackingHttpClient client, int Year, string project, string path=null) {
this.client = client;
node = new WorkItemClassificationNode();
this.project = project;
this.path = path;
IDictionary<string, object> DateAttr = new Dictionary<string, object>();
DateAttr.Add("startDate", new DateTime(Year, 1, 1));
DateAttr.Add("finishDate", new DateTime(Year, 12, 31));
node.Attributes = DateAttr;
node.Name = Year.ToString();
node.StructureType = TreeNodeStructureType.Iteration;
}
public void SaveNode()
{
var res = client.CreateOrUpdateClassificationNodeAsync(node, project, TreeStructureGroup.Iterations, path).Result;
Console.WriteLine(res.Id);
}
}
See below result:
I can reproduce above error Value cannot be null. Parameter name: nodeName. If i intentionally didnot set the node.Name = null; You can debug your code to check why the node name was not set.

EF Core, LINQ Operator '=' in VB.NET

I have the next Code in EF Core 3.1 in language VB.NET
Dim supplierID as string="1545464"
Dim results = (From pa In DC.product.AsNoTracking()
Where pa.supplierID = supplierID
Select pa)
The exception throw is:
The LINQ expression 'DbSet<product>
.Where(p => Operators.CompareString(
Left: p.supplierID,
Right: __$VB$Local_supplierID_0,
TextCompare: False) == 0)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync().
I found the following solution:
Dim supplierID as string="1545464"
Dim results = (From pa In DC.product.AsNoTracking()
Where pa.supplierID.Equals(supplierID)
Select pa)
Is my solution correct, using .Equals()? In C# language if it works with the operator "=="
I have created a small solution with which you can reproduce the error.
The solution has 4 projects:
Sup.Entities (C#)
Sup.DAL (C#)
Sup.ConsoleApp1 (C#)
Sup.consoleAppVB (VB.NET)
This is the error that occurs in Sup.ConsoleAppVB (VB.NET)
This is the result in Sup.ConsoleApp1 (C#)
Attached solution Download that includes projects and an SQL file to create the database and 1 table with 3 rows.
Please change connectionstring for UseSqlServer("...") in OnConfiguring Context
I just ran into this issue but since I have developed my own LINQ to SQL evaluator before I knew how to solve the problem. VB.NET transforms the = operator for strings into a call to Microsoft.VisualBasic.CompilerServices.Operators.CompareString(). So when the expression tree is evaluated this method must be handled. I suspect the reason for this is because VB handles string comparisons to null ((text = Nothing) = True).
I didn't download your sample but I fixed it in an ASP.NET Core application.
If you were using LINQ, this would be handled inside an ExpressionVisitor but for Entity Framework Core 3.1, I found that you can implement an IMethodCallTranslator.
However, Entity Framework uses SqlExpression expressions so an ISqlExpressionFactory is needed to create them. Fortunately, dependency injection can be used to get an implementation from IServiceCollection.
Public Class VbCompareStringMethodCallTranslator : Implements IMethodCallTranslator
Private mExpressionFactory As ISqlExpressionFactory
Public Sub New(expressionFactory As ISqlExpressionFactory)
Me.mExpressionFactory = expressionFactory
End Sub
Public Function Translate(instance As SqlExpression, method As MethodInfo, arguments As IReadOnlyList(Of SqlExpression)) As SqlExpression Implements IMethodCallTranslator.Translate
If method IsNot Nothing Then
If method.Name = "CompareString" AndAlso method.DeclaringType?.Name = "Operators" AndAlso
method.DeclaringType?.Namespace = "Microsoft.VisualBasic.CompilerServices" Then
Dim left = arguments(0)
Dim right = arguments(1)
If method.Name Is NameOf(String.Compare) AndAlso arguments.Count = 2 AndAlso
arguments(0).Type.UnwrapNullableType Is arguments(1).Type.UnwrapNullableType Then
left = arguments(0)
right = arguments(1)
ElseIf method.Name Is NameOf(String.CompareTo) AndAlso arguments.Count = 1 AndAlso
instance IsNot Nothing AndAlso instance.Type.UnwrapNullableType Is arguments(0).Type.UnwrapNullableType Then
left = instance
right = arguments(0)
End If
If left IsNot Nothing AndAlso right IsNot Nothing Then
Return Me.mExpressionFactory.[Case]({New CaseWhenClause(Me.mExpressionFactory.Equal(left, right), Me.mExpressionFactory.Constant(0)),
New CaseWhenClause(Me.mExpressionFactory.GreaterThan(left, right), Me.mExpressionFactory.Constant(1)),
New CaseWhenClause(Me.mExpressionFactory.LessThan(left, right), Me.mExpressionFactory.Constant(-1))},
Nothing)
End If
End If
End If
Return Nothing
End Function
End Class
Making use of the following extension method
Public Module SharedTypeExtensions
<Extension()>
Public Function UnwrapNullableType(type As Type) As Type
Return If(Nullable.GetUnderlyingType(type), type)
End Function
End Module
You can see that this is the code used by Entity Framework to handle string comparisons here https://github.com/dotnet/efcore/blob/3656e9daa9b81398d8c065a702fd5dca91979f49/src/EFCore.Relational/Query/Internal/ComparisonTranslator.cs
So now this needs to be hooked up and the following plumbing code can be used
Public Class VbMethodCallTranslatorPlugin : Implements IMethodCallTranslatorPlugin
Public Sub New(expressionFactory As ISqlExpressionFactory)
Me.Translators = {New VbCompareStringMethodCallTranslator(expressionFactory)}
End Sub
Public ReadOnly Property Translators As IEnumerable(Of IMethodCallTranslator) Implements IMethodCallTranslatorPlugin.Translators
End Class
Public Class VbDbContextOptionsExtension : Implements IDbContextOptionsExtension
Public Sub ApplyServices(services As IServiceCollection) Implements IDbContextOptionsExtension.ApplyServices
services.AddSingleton(Of IMethodCallTranslatorPlugin, VbMethodCallTranslatorPlugin)
End Sub
Public Sub Validate(options As IDbContextOptions) Implements IDbContextOptionsExtension.Validate
End Sub
Public ReadOnly Property Info As DbContextOptionsExtensionInfo Implements IDbContextOptionsExtension.Info
Get
Return New VbDbContextOptionsExtensionInfo(Me)
End Get
End Property
End Class
Public Class VbDbContextOptionsExtensionInfo : Inherits DbContextOptionsExtensionInfo
Public Sub New(extension As IDbContextOptionsExtension)
MyBase.New(extension)
End Sub
Public Overrides Function GetServiceProviderHashCode() As Long
Return Me.Extension.GetHashCode
End Function
Public Overrides Sub PopulateDebugInfo(<NotNullAttribute> debugInfo As IDictionary(Of String, String))
debugInfo("VB:TranslateMethods") = True.ToString
End Sub
Public Overrides ReadOnly Property IsDatabaseProvider As Boolean
Get
Return False
End Get
End Property
Public Overrides ReadOnly Property LogFragment As String
Get
Return "VbMethodSupport=true"
End Get
End Property
End Class
Now this can hooked up using the DbContextOptionsBuilder, but the following extension method will make this easier
Public Module VbDbContextOptionsBuilderExtensions
<Extension>
Public Function AddVbSupport(optionsBuilder As DbContextOptionsBuilder) As DbContextOptionsBuilder
Dim builder = CType(optionsBuilder, IDbContextOptionsBuilderInfrastructure)
Dim extension = If(optionsBuilder.Options.FindExtension(Of VbDbContextOptionsExtension), New VbDbContextOptionsExtension)
builder.AddOrUpdateExtension(extension)
Return optionsBuilder
End Function
End Module
Now you can hook this up while setting up your DbContext
services.AddDbContext(Of ApplicationDbContext)(Sub(options)
options.UseSqlServer(Me.Configuration.GetConnectionString("ConnectionString"),
Sub(dbOptions)
dbOptions.MigrationsAssembly("Database.Migrations")
End Sub)
options.AddVbSupport
End Sub)
Additional Info
This appears to be a bug in Entity Framework rather than VB.NET just not being supported. You can find this code in the dotnet efcore repository.
https://github.com/dotnet/efcore/blob/7cb52b388a2d9fd8f9c2c499ef3ffb9753d9932a/src/EFCore/Query/Internal/QueryOptimizingExpressionVisitor.cs#L113-L132
I submitted a bug report here
https://github.com/dotnet/efcore/issues/20889
Vote it up so the devs will fix the issue!
Update 1
Looks like this will be fixed in .NET 5
Update 2
The above solution was causing issues after refreshing the page a bunch of times. I would get an error something to the effect of "more than 20 IService instances have been created"
In order to fix this I just added the expression transform into a different part of the pipeline.
Imports System.Linq.Expressions
Imports System.Runtime.CompilerServices
Imports Microsoft.EntityFrameworkCore
Imports Microsoft.EntityFrameworkCore.Query
Public Class VbRelationalQueryTranslationPreprocessorFactory : Implements IQueryTranslationPreprocessorFactory
Private ReadOnly mDependencies As QueryTranslationPreprocessorDependencies
Private ReadOnly mRelationalDependencies As RelationalQueryTranslationPreprocessorDependencies
Public Sub New(dependencies As QueryTranslationPreprocessorDependencies, relationalDependencies As RelationalQueryTranslationPreprocessorDependencies)
Me.mDependencies = dependencies
Me.mRelationalDependencies = relationalDependencies
End Sub
Public Overridable Function Create(queryCompilationContext As QueryCompilationContext) As QueryTranslationPreprocessor Implements IQueryTranslationPreprocessorFactory.Create
Return New VbRelationalQueryTranslationPreprocessor(Me.mDependencies, Me.mRelationalDependencies, queryCompilationContext)
End Function
End Class
Public Class VbRelationalQueryTranslationPreprocessor : Inherits RelationalQueryTranslationPreprocessor
Public Sub New(dependencies As QueryTranslationPreprocessorDependencies, relationalDependencies As RelationalQueryTranslationPreprocessorDependencies, queryCompilationContext As QueryCompilationContext)
MyBase.New(dependencies, relationalDependencies, queryCompilationContext)
End Sub
Public Overrides Function Process(query As Expression) As Expression
query = New LanguageNormalizingExpressionVisitor().Visit(query)
Return MyBase.Process(query)
End Function
End Class
Public Class LanguageNormalizingExpressionVisitor : Inherits ExpressionVisitor
Protected Overrides Function VisitBinary(node As BinaryExpression) As Expression
Dim methodCall = TryCast(node.Left, MethodCallExpression)
If methodCall IsNot Nothing Then
' Replace calls to comparestring with a binary equals on the operands
If methodCall.Method.Name = "CompareString" AndAlso methodCall.Method.DeclaringType?.Name = "Operators" AndAlso methodCall.Method.DeclaringType?.Namespace = "Microsoft.VisualBasic.CompilerServices" Then
Dim left = Me.Visit(methodCall.Arguments(0))
Dim right = Me.Visit(methodCall.Arguments(1))
Return Expression.MakeBinary(node.NodeType, left, right)
End If
End If
Return MyBase.VisitBinary(node)
End Function
End Class
Public Module VbDbContextOptionsBuilderExtensions
<Extension>
Public Function AddVbSupport(optionsBuilder As DbContextOptionsBuilder) As DbContextOptionsBuilder
optionsBuilder.ReplaceService(Of IQueryTranslationPreprocessorFactory, VbRelationalQueryTranslationPreprocessorFactory)()
Return optionsBuilder
End Function
End Module

Action in controller with two parameters - how to set up route and controller?

I have this controller:
Public Class UsersController
Inherits ApiController
Public reportsjsonfilepath = System.AppDomain.CurrentDomain.BaseDirectory & "\reports.json"
<HttpGet>
<Route("")>
Public Function Index() As HttpResponseMessage
Log.Information("Main index requested at {0}", DateTime.Now)
Dim response As StringBuilder = New StringBuilder
response.Append("Index requested at: " & DateTime.Now)
response.Append("<br>")
response.Append("Hello, this is a test WebApi server!")
Dim raspuns = String.Join("/n", response.ToString)
Dim raspunsindex = Request.CreateResponse(Of String)(HttpStatusCode.OK, raspuns)
raspunsindex.Content.Headers.ContentType = New MediaTypeHeaderValue("text/html")
Return raspunsindex
End Function
<HttpGet>
<Route("users")>
Public Function Users() As HttpResponseMessage
Log.Information("Users index requested at {0}", DateTime.Now)
Dim response As StringBuilder = New StringBuilder()
Dim dictionarusers As IDictionary(Of String, String) = GetUsersList()
Dim i As Integer = 0
For Each entry As KeyValuePair(Of String, String) In dictionarusers
i = i + 1
response.Append(i)
response.Append(" - ")
response.Append(entry.Value)
response.Append("<br>")
Next
Dim raspuns = Request.CreateResponse(Of String)(HttpStatusCode.OK, response.ToString)
raspuns.Content.Headers.ContentType = New MediaTypeHeaderValue("text/html")
Return raspuns
End Function
<HttpGet>
Public Function GetQlikLink(username As String, reportId As Integer) As HttpResponseMessage
QlikLink.GetLink(username, reportId)
End Function
End Class
The routes are set up like this:
Public Module RoutesConfig
<Extension()>
Sub MapDefinedRoutes(ByVal config As HttpConfiguration)
config.Routes.MapHttpRoute(name:="Relevance", routeTemplate:="api/{controller}", defaults:=New With {
.id = RouteParameter.[Optional]
})
config.Routes.MapHttpRoute("QlikLink", "api/{controller}/{action}/{id}", New With {
.id = RouteParameter.[Optional]
})
End Sub
End Module
Now, when I go http://localhost:9000/relevance, the index kicks in OK. Same for http://localhost:9000/relevance/users. But how one must set-up the action and the route to get something by getting the params from the request? How the parameters are sent: ?username=somestring&?id=2? I am talking about function GetQlikLink, the last one from the Controller.
Any hint will be appreciated! Thanks a lot!
For actions where the parameters are different to those defined in the routing configuration, then the query string keys should be the same as the function parameter names.
http://..../GetQlikLink?username=xyz&reportId=1234
I would recommend reading up about the different types of routing.
The config which you have shown looks like convention based routing.
However, you also appear to be adding routing attributes to your action methods.
Convention based routing lets you define route templates, and the pattern of things like {controller} and {action} must match the names of the controller and actions.
Routing and Action Selection in ASP.NET Web API
The other articles in this section also describe attribute routing.

How to change function implementation for automatically generated partial class?

Visual Studio automatically generated a partial class in my project, containing a function whose implementation I want to change.
Is this doable without losing the changes I made whenever the class is automatically generated again?
Example:
Partial Public Class INO_Opportunity
Private Function IsLocalFileSystemWebService(ByVal url As String) As Boolean
' Here I want to add code
' All the following code is generated by VS:
If ((url Is Nothing) OrElse (url Is String.Empty)) Then
Return False
End If
Dim wsUri As System.Uri = New System.Uri(url)
If ((wsUri.Port >= 1024) AndAlso (String.Compare(wsUri.Host, "localHost", System.StringComparison.OrdinalIgnoreCase) = 0)) Then
Return True
End If
Return False
End Function
End Class

VB.net creating new AD user account using UserPrincipalEx?

I'm having a heck of a time trying to add fields like department and title.
I'm using this to create a user account:
Dim ctx As New PrincipalContext(ContextType.Domain, "domain.name.pvt", "OU=Users,DC=global,DC=pvt")
Dim usr As UserPrincipal = New UserPrincipal(ctx)
I have no problem creating the account but can't add simple things like Department and Title. I read about using extensions but its in C++ and have no clue on how to do it.
Any help would be great!!! Thanks!
If you're on .NET 3.5 and up, you should check out the System.DirectoryServices.AccountManagement (S.DS.AM) namespace. Read all about it here:
Managing Directory Security Principals in the .NET Framework 3.5
MSDN docs on System.DirectoryServices.AccountManagement
To extend the UserPrincipal class, you don't need much - something like this will suffice (I wrote this in C# originally and just converted it to VB.NET on the 'net - I hope there's no issues with the VB.NET code!)
Imports System.DirectoryServices.AccountManagement
Namespace ADExtended
<DirectoryRdnPrefix("CN")> _
<DirectoryObjectClass("Person")> _
Public Class UserPrincipalEx
Inherits UserPrincipal
' Inplement the constructor using the base class constructor.
Public Sub New(context As PrincipalContext)
MyBase.New(context)
End Sub
' Implement the constructor with initialization parameters.
Public Sub New(context As PrincipalContext, samAccountName As String, password As String, enabled As Boolean)
MyBase.New(context, samAccountName, password, enabled)
End Sub
' Create the "Department" property.
<DirectoryProperty("department")> _
Public Property Department() As String
Get
If ExtensionGet("department").Length <> 1 Then
Return String.Empty
End If
Return DirectCast(ExtensionGet("department")(0), String)
End Get
Set
ExtensionSet("department", value)
End Set
End Property
' Create the "Title" property.
<DirectoryProperty("title")> _
Public Property Title() As String
Get
If ExtensionGet("title").Length <> 1 Then
Return String.Empty
End If
Return DirectCast(ExtensionGet("title")(0), String)
End Get
Set
ExtensionSet("title", value)
End Set
End Property
' Implement the overloaded search method FindByIdentity.
Public Shared Shadows Function FindByIdentity(context As PrincipalContext, identityValue As String) As UserPrincipalEx
Return DirectCast(FindByIdentityWithType(context, GetType(UserPrincipalEx), identityValue), UserPrincipalEx)
End Function
' Implement the overloaded search method FindByIdentity.
Public Shared Shadows Function FindByIdentity(context As PrincipalContext, identityType As IdentityType, identityValue As String) As UserPrincipalEx
Return DirectCast(FindByIdentityWithType(context, GetType(UserPrincipalEx), identityType, identityValue), UserPrincipalEx)
End Function
End Class
End Namespace
Now, you just use the UserPrincipalEx class:
Dim ctx As New PrincipalContext(ContextType.Domain, "domain.name.pvt", "OU=Users,DC=global,DC=pvt")
Dim usr As UserPrincipalEx = New UserPrincipalEx(ctx)
usr.Title = "......."
usr.Department = "......."
The new S.DS.AM makes it really easy to play around with users and groups in AD!