How to change function implementation for automatically generated partial class? - vb.net

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

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.

How to enumerate My.Setting.Connectionstring in custom control Combobox

I am creating a custom control of type Textbox, and I want to create a combobox property that enumerates all the connections available in the project setting "Connection String".
I am using this code. As you can see, I commented non-working code while I was trying.
Private _formatString As String = Nothing
<Category("Display")>
<DisplayName("Connection")>
<Description("Connection string.")>
<DefaultValue("")>
<TypeConverter(GetType(FormatStringConverter))>
Public Property Connection As String
Get
Return _formatString
End Get
Set(ByVal value As String)
_formatString = value
End Set
End Property
Public Class FormatStringConverter
Inherits StringConverter
Public Overrides Function GetStandardValuesSupported(ByVal context As ITypeDescriptorContext) As Boolean
Return True
End Function
Public Overrides Function GetStandardValuesExclusive(ByVal context As ITypeDescriptorContext) As Boolean
Return True
End Function
Public Overrides Function GetStandardValues(ByVal context As ITypeDescriptorContext) As TypeConverter.StandardValuesCollection
Dim list As List(Of String) = New List(Of String)()
'For Each current In Configuration.ConfigurationManager.ConnectionStrings
' list.Add(current.Name)
'Next
'For Each value As Configuration.SettingsPropertyValue In My.Settings.PropertyValues
' 'MessageBox.Show(value.Name & " - " & value.PropertyValue.ToString)
' 'list.Add(value.Name & " - " & value.PropertyValue.ToString)
'Next
list.Add("")
list.Add("Currency")
list.Add("Scientific Notation")
list.Add("General Number")
list.Add("Number")
list.Add("Percent")
list.Add("Time")
list.Add("Date")
Return New StandardValuesCollection(list)
End Function
End Class
I want to enumerate the connection strings in the project I placed the control in.
The custom control is in a separate project, not the same project of the form I am using.
[edit]
I cannot see the expected results at design-time. I want to choose the connection from a combobox same as tableadapter in dataset table adapter connection selection from combobox at design time. So I want to read and set the connection list available in a form belong to the project I placed the custom textbox in at design time.

How to properly render an embedded Font?

I download a True Type Font and I embedded it just as this page explains.
I had to set the UseCompatibleTextRendering property to be able to load it but it looks very weird, I don't know why it looks good in the browser but not in the application.
Just to be clear I added the font to my resources, set it as embedded resource, I used this module:
Imports System.IO
Imports System.Reflection
Imports System.Drawing.Text
Imports System.Runtime.InteropServices
Module ExternalFontType
Public Function GetFont(aAssembly As Assembly,
strFontName As String, intFontSize As Integer,
fsFontStyle As FontStyle) As Font
Using pcolFonts As New PrivateFontCollection
Dim bFont() As Byte = ExternalFontType.bRawFontData(aAssembly, strFontName)
Dim ptrMemFont As IntPtr =
Marshal.AllocCoTaskMem(bFont.Length)
Marshal.Copy(bFont, 0, ptrMemFont, bFont.Length)
pcolFonts.AddMemoryFont(ptrMemFont, bFont.Length)
Marshal.FreeCoTaskMem(ptrMemFont)
Return New Font(pcolFonts.Families(0),
intFontSize, fsFontStyle)
End Using
End Function
Private Function bRawFontData(aAssembly As Assembly, strFontName As String) As Byte()
Using stFont As Stream =
aAssembly.GetManifestResourceStream(strFontName)
If (stFont Is Nothing) Then Throw _
New Exception(String.Format("Cannot load _
font '{0}'", strFontName))
Dim bFontBuffer() As Byte = New _
Byte(CInt(stFont.Length - 1)) {}
stFont.Read(bFontBuffer, 0, CInt(stFont.Length))
Return bFontBuffer
End Using
End Function
End Module
and included it in this code
lbl.UseCompatibleTextRendering = True
lbl.Font = ExternalFontType.GetFont(Me.GetType.Assembly, "ProyectName.FontName.ttf", 15, FontStyle.Bold)
More than one problem with that code:
The PrivateFontCollection cannot be declared with a Using statement: this collection must be preserved as long as the Fonts it points to are needed. It's usually declared as a Field in the class (Form) that uses it or in a shared class (or Module, here), then disposed of when not needed anymore.
Marshal.FreeCoTaskMem() cannot be used here; it's a temptation to call it after Marshal.AllocCoTaskMem(), but not in this occasion. This can (will) compromise the Font data allocation. What you need to do is dispose of the PrivateFontcollection object. The Framework will take care of the COM affair (it will do it for you even if you forget to dispose of the PrivateFontcollection object. You should try not to forget, though).
The assembly reference is not required: the Font is added to the Project's Resources as a byte array, which is all that's needed. It can then be retrieved either by name, e.g., My.Resources.SomeFontName, or using the ResourceManager.GetObject() method, casting the returned object to Byte():
Dim fontData As Byte() = My.Resources.SomeFontName
Dim fontData As Byte() = DirectCast(My.Resources.ResourceManager.GetObject("SomeFontName"), Byte())
▶ You have already mentioned this but let's say it again: not all controls can use these Fonts. Only controls that can use Fonts drawn by GDI+ can actually use Fonts from the PrivateFontCollection, the Label and Button controls are among of these, in fact both expose a UseCompatibleTextRendering property. A RichTextBox, for example, cannot.
If the Font is created correctly, you can use Graphics.DrawString() to draw strings content using that Font, even when you cannot set it as the Font of a Control.
Private myFontCollection As PrivateFontCollection = New PrivateFontCollection()
In the Form's Constructor, add Font from the Project's Resources.
Here I'm using a helper class, FontManager, which exposes a public shared method AddFontsFromResource(): pass to this method the PrivateFontCollection and a list of resources names corresponding to the Font names.
This method fills the collection with Fonts that can be installed successfully and returns the number of Fonts installed.
Of course you use whatever other method you prefer to reference your Fonts.
Note. In the example, three Font resources are added to the collection:
{"FontFamily1Regular", "FontFamily1Italics", "OtherFontFamily"}
but two belong to the same FontFamily, so the PrivateFontCollection will contain just two elements, not three.
Public Sub New()
Dim installedFontsCount = FontManager.AddFontsFromResources(myFontCollection,
{"FontFamily1Regular", "FontFamily1Italics", "OtherFontFamily"})
' The Font can set here or anywhere else
someLabel.UseCompatibleTextRendering = True
someLabel.Font = New Font(myFontCollection.Families(0), 10.5F, FontStyle.Regular)
someButton.UseCompatibleTextRendering = True
someButton.Font = New Font(myFontCollection.Families(0), 10.5F, FontStyle.Italic)
End Sub
It's important to dispose of the PrivateFontCollection when it's not needed anymore: when the Form that initialized it closes or before the Application closes:
You could also use a shared object to reference a PrivateFontCollection that can be used anywhere in the Project. In this case the collection needs to be disposed of when the Application closes.
Private Sub Form1_FormClosed(sender As Object, e As FormClosedEventArgs) Handles MyBase.FormClosed
myFontCollection.Dispose()
End Sub
Helper class:
Imports System.Drawing.Text
Imports System.Runtime.InteropServices
Public Class FontManager
Public Shared Function AddFontsFromResources(fontCollection As PrivateFontCollection, fontNames As String()) As Integer
If fontNames.Length = 0 Then Return Nothing
Dim installedFontsCount = 0
For Each fontName As String In fontNames
Try
Dim fontData As Byte() = CType(My.Resources.ResourceManager.GetObject(fontName), Byte())
If fontData Is Nothing Then Throw New InvalidOperationException()
Dim data As IntPtr = Marshal.AllocCoTaskMem(fontData.Length)
Marshal.Copy(fontData, 0, data, fontData.Length)
fontCollection.AddMemoryFont(data, fontData.Length)
installedFontsCount += 1
Catch ex As Exception
' Placeholder: Notify User/Log/Whatever
Debug.Print($"Font installation failed for {fontName}")
End Try
Next
Return installedFontsCount
End Function
End Class
C# version:
using System.Drawing.Text;
using System.Runtime.InteropServices;
public static int AddFontsFromResources(PrivateFontCollection fontCollection, string[] fontNames)
{
int installedFontsCount = 0;
if (fontNames.Length == 0) return 0;
foreach (string fontName in fontNames) {
try {
byte[] fontData = (byte[])Properties.Resources.ResourceManager.GetObject(fontName);
var data = Marshal.AllocCoTaskMem(fontData.Length);
Marshal.Copy(fontData, 0, data, fontData.Length);
fontCollection.AddMemoryFont(data, fontData.Length);
installedFontsCount += 1;
}
catch (Exception) {
// Placeholder: Notify User/Log/Whatever
Console.WriteLine($"Font installation failed for {fontName}");
}
}
return installedFontsCount;
}

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

ASP.NET MVC Structure/Logic

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.