I seem to be stuck at developing a custom Key/Value pair provider for Amazon's System Manager Parameter Store (SSM) using NETFramework 4.7.1 that utilizes Microsoft.Configuration.ConfigurationBuilders.
The implementation:
using System;
using System.Collections.Generic;
using Amazon.SimpleSystemsManagement;
using Amazon.SimpleSystemsManagement.Model;
using Microsoft.Configuration.ConfigurationBuilders;
using System.Linq;
using System.Diagnostics;
using System.Collections.Specialized;
using Amazon.Runtime;
using Amazon.Runtime.CredentialManagement;
using System.Configuration;
using System.Threading.Tasks;
namespace AXS.Configurations
{
public class ParameterStoreConfigBuilder : KeyValueConfigBuilder
{
public const string envTag = "Environment";
public const string appNameTag = "AppName";
private IAmazonSimpleSystemsManagement client;
/// <summary>
/// Gets or sets an environment (dev|qa|staging|production)
/// </summary>
public string Environment { get; set; }
/// <summary>
/// Gets or sets a AppName
/// </summary>
public string AppName { get; set; }
public ParameterStoreConfigBuilder(IAmazonSimpleSystemsManagement client,
string appName,
string environment)
{
this.client = client;
Environment = environment.ToLower();
AppName = appName;
}
public ParameterStoreConfigBuilder()
{
client = new AmazonSimpleSystemsManagementClient();
}
public override string Description => "Parameter Store";
public override string Name => "SSM";
protected override void LazyInitialize(string name, NameValueCollection config)
{
Optional = false;
base.LazyInitialize(name, config);
string env = UpdateConfigSettingWithAppSettings(envTag);
if (string.IsNullOrWhiteSpace(env))
throw new ArgumentException($"environment must be specified with the '{envTag}' attribute.");
Environment = env;
string appName = UpdateConfigSettingWithAppSettings(appNameTag);
if (string.IsNullOrWhiteSpace(appName))
throw new ArgumentException($"appName must be specified with the '{appNameTag}' attribute.");
AppName = appName;
client = new AmazonSimpleSystemsManagementClient("","", Amazon.RegionEndpoint.USWest2);
}
public override ICollection<KeyValuePair<string, string>> GetAllValues(string prefix)
{
Trace.TraceInformation($"return values prefix {prefix}");
if (client == null)
return null;
var parameters = new List<Parameter>();
string nextToken = null;
do
{
var response = client.GetParametersByPath(new GetParametersByPathRequest { Path = prefix, Recursive = true, WithDecryption = true, NextToken = nextToken });
nextToken = response.NextToken;
parameters.AddRange(response.Parameters);
} while (!string.IsNullOrEmpty(nextToken));
return parameters.Select(p => new
{
Key = p.Name,
p.Value
}).ToDictionary(parameter => parameter.Key, parameter => parameter.Value, StringComparer.OrdinalIgnoreCase);
}
public override string GetValue(string key)
{
return Task.Run(async () => { return await GetValueAsync(key); }).Result;
}
private async Task<string> GetValueAsync(string key)
{
var name = $"/{Environment}/{AppName}/{key.Replace(':', '/')}";
Trace.WriteLine($"get value async:{name}");
if (client == null)
return null;
try
{
Trace.TraceInformation($"fetch key {name}");
var request = new GetParameterRequest
{
Name = name,
WithDecryption = true
};
var response = await client.GetParameterAsync(request);
var parameter = response.Parameter;
var value = parameter.Type == ParameterType.SecureString ? "*****" : parameter.Value;
Trace.TraceInformation($"fetched name={name} value={value}");
return value;
}
catch (Exception e) when (Optional && ((e.InnerException is System.Net.Http.HttpRequestException) || (e.InnerException is UnauthorizedAccessException))) { }
return null;
}
}
}
The problem seems to be that AWS SSM client never gets created.
If I change the code and try to instantiate in the constructor I get a stack overflow exception due to recursion.
Any ideas on how to force to get AmazonSimpleSystemsManagementClient created?
The code uses guidance from https://github.com/aspnet/MicrosoftConfigurationBuilders
The App.Config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="configBuilders" type="System.Configuration.ConfigurationBuildersSection,
System.Configuration, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a"
restartOnExternalChanges="false"
requirePermission="true" />
</configSections>
<configBuilders>
<builders>
<add name="ParameterStore" Environment="development" AppName="myAppNameforParmStore" type="AXS.Configurations.ParameterStoreConfigBuilder, AXS.Configurations" />
<add name="Env" prefix="appsettings_" stripPrefix="true" type="Microsoft.Configuration.ConfigurationBuilders.EnvironmentConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.Environment, Version=2.0.0.0, Culture=neutral" />
</builders>
</configBuilders>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.1" />
</startup>
<appSettings configBuilders="Env,ParameterStore">
<add key="Url" value="URL Value for from paramter Store" />
<add key="Secret" value="Some Secret value decrypted" />
</appSettings>
</configuration>
Thanks
UPDATE
I posted an updated version of the AwsSsmConfigurationBuilder, and a sample ASP.NET Web Forms project that uses it, on my GitHub:
https://github.com/Kirkaiya/AwsSsmConfigBuilderPoC/
Disclaimer: This is a proof-of-concept (POC) for a custom ConfigurationBuilder for ASP.NET 4.7.1 or higher (running on .NET Framework obviously). It's a POC, so it doesn't do anything besides allow you store Configuration AppSettings in AWS Parameter Store (a feature of Simple Systems Manager). So, clearly, don't use this in production without productizing and testing it!
Prerequisites:
Your project must target .NET Framework 4.7.1 or higher
Include NuGet package Microsoft.Configuration.ConfigurationBuilders.Base
Have parameters in AWS SSM Parameter Store that have the same name (not counting the prefix) as parameters in your web.config file, and vice-versa.
Notes
In order to avoid recursively calling a concrete constructor or Initialize, I used a static constructor to instantiate the AmazonSimpleSystemsManagementClient, which is held in a static member.
Web.Config additions
Note: change the assembly/class-name of your builder to match yours, etc.
<configSections>
<section name="configBuilders" type="System.Configuration.ConfigurationBuildersSection, System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" restartOnExternalChanges="false" requirePermission="false" />
</configSections>
<configBuilders>
<builders>
<add name="ParameterStore" ssmPrefix="/padnugapp/ApiKeys" type="Microsoft.Configuration.ConfigurationBuilders.AwsSsmConfigBuilder, AspNetWebFormsSample" />
</builders>
</configBuilders>
<appSettings configBuilders="ParameterStore">
<add key="TestKey" value="TestKey Value from web.config" />
<add key="TwitterKey" value="TwitterKey value from web.config" />
</appSettings>
And the AwsSsmConfigBuilder.cs file:
namespace Microsoft.Configuration.ConfigurationBuilders
{
public class AwsSsmConfigBuilder : KeyValueConfigBuilder
{
private string BaseParameterPath = "/padnugapp/ApiKeys";
private static IAmazonSimpleSystemsManagement _client;
static AwsSsmConfigBuilder()
{
_client = new AmazonSimpleSystemsManagementClient();
}
public override void Initialize(string name, NameValueCollection config)
{
base.Initialize(name, config);
if (config["ssmPrefix"] == null)
return;
BaseParameterPath = config["ssmPrefix"];
}
public override ICollection<KeyValuePair<string, string>> GetAllValues(string prefix)
{
if (_client == null)
return null;
var request = new GetParametersByPathRequest
{
Path = $"{BaseParameterPath}/{prefix}",
WithDecryption = true,
};
var response = _client.GetParametersByPathAsync(request).Result;
var result = response.Parameters.ToDictionary(param => param.Name, param => param.Value, StringComparer.OrdinalIgnoreCase);
return result;
}
public override string GetValue(string key)
{
if (_client == null)
return null;
var request = new GetParameterRequest
{
Name = $"{BaseParameterPath}/{key}",
WithDecryption = true,
};
var response = _client.GetParameterAsync(request).Result;
return response.Parameter.Value;
}
}
}
The code I put into a web-forms (.aspx) page that renders the two appSettings items in HTML:
TestKey =
<%=(System.Configuration.ConfigurationManager.AppSettings["TestKey"]) %>
<br />
TwitterKey =
<%=(System.Configuration.ConfigurationManager.AppSettings["TwitterKey"]) %>
I can't stress enough that this is just for a demo I'm doing, and not tested in any way, shape or form except on my laptop ;-)
Related
I am trying to adapt the Microsoft example for AF 3.0/.NET Core 3.1/xUnit (see Strategies for testing your code in Azure Functions) to work with AF 3.0/.NET 5.0/xUnit. However, I am running into compilation issues.
The Azure Function is a simple HTTP Trigger (GET only), ExportFuncApp.csproj file is as follows:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<AzureFunctionsVersion>v3</AzureFunctionsVersion>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.0.12" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.2.0" OutputItemType="Analyzer" />
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.5.2" />
</ItemGroup>
<ItemGroup>
<None Update="host.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="local.settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
</ItemGroup>
</Project>
The ExportFunc.cs file is as follows:
using System.Net;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
namespace ExportFuncApp
{
public class ExportFunc
{
[Function(nameof(ExportFunc))]
public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequestData req,
FunctionContext executionContext)
{
var logger = executionContext.GetLogger("ExportFunc");
logger.LogInformation("C# HTTP trigger function processed a request.");
var response = req.CreateResponse(HttpStatusCode.OK);
response.Headers.Add("Content-Type", "text/plain; charset=utf-8");
response.WriteString("Welcome to Azure Functions!");
return response;
}
}
}
Nothing special there. However, XUnit tests provided by Microsoft (.NET Core 3.1) are not really applicable to .NET 5.0. There was a StackOverflow article on the subject: Testing an Azure Function in .NET 5. 4 solutions were given in the article and all of them have compile issues. The first solution given was (ExportFuncUnitTests2.cs):
using Xunit;
using ExportFuncApp;
using System.Threading.Tasks;
using System.IO;
using System.Text;
using Moq;
using Microsoft.Azure.Functions.Worker;
using System.Net;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.Functions.Worker.Http;
namespace ExportFuncAppUnitTestsXunit
{
public class ExportFuncUnitTests2
{
[Fact]
public async Task Http_trigger_should_return_known_string()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddScoped<ILoggerFactory, LoggerFactory>();
var serviceProvider = serviceCollection.BuildServiceProvider();
var context = new Mock<FunctionContext>();
context.SetupProperty(c => c.InstanceServices, serviceProvider);
var byteArray = Encoding.ASCII.GetBytes("test");
var bodyStream = new MemoryStream(byteArray);
var request = new Mock<HttpRequestData>(context.Object);
request.Setup(r => r.Body).Returns(bodyStream);
request.Setup(r => r.CreateResponse()).Returns(() =>
{
var response = new Mock<HttpResponseData>(context.Object);
response.SetupProperty(r => r.Headers, new HttpHeadersCollection());
response.SetupProperty(r => r.StatusCode);
response.SetupProperty(r => r.Body, new MemoryStream());
return response.Object;
});
var result = await ExportFunc.Run(request.Object, context.Object);
result.HttpResponse.Body.Seek(0, SeekOrigin.Begin);
// Assert
var reader = new StreamReader(result.HttpResponse.Body);
var responseBody = await reader.ReadToEndAsync();
Assert.NotNull(result);
Assert.Equal(HttpStatusCode.OK, result.HttpResponse.StatusCode);
Assert.Equal("Hello test", responseBody);
}
}
}
This results in a compilation error in ExportFuncUnitTests2.cs:
CS1061 'HttpResponseData' does not contain a definition for 'GetAwaiter' and no accessible extension method 'GetAwaiter' accepting a first argument of type 'HttpResponseData' could be found (are you missing a using directive or an assembly reference?)
for:
var result = await ExportFunc.Run(request.Object, context.Object);
The second solution given in the article involves FakeHttpRequestData.cs:
using System;
using System.Collections.Generic;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Azure.Functions.Worker;
using System.IO;
using System.Security.Claims;
namespace ExportFuncAppUnitTestsXunit
{
class FakeHttpRequestData : HttpRequestData
{
public FakeHttpRequestData(FunctionContext functionContext, Uri url, Stream body = null) : base(functionContext)
{
Url = url;
Body = body ?? new MemoryStream();
}
public override Stream Body { get; } = new MemoryStream();
public override HttpHeadersCollection Headers { get; } = new HttpHeadersCollection();
public override IReadOnlyCollection<IHttpCookie> Cookies { get; }
public override Uri Url { get; }
public override IEnumerable<ClaimsIdentity> Identities { get; }
public override string Method { get; }
public override HttpResponseData CreateResponse()
{
return new FakeHttpResponseData(FunctionContext);
}
}
}
and, FakeHttpResponseData.cs:
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Azure.Functions.Worker;
using System.Net;
using System.IO;
namespace ExportFuncAppUnitTestsXunit
{
class FakeHttpResponseData : HttpResponseData
{
public FakeHttpResponseData(FunctionContext functionContext) : base(functionContext)
{
}
public override HttpStatusCode StatusCode { get; set; }
public override HttpHeadersCollection Headers { get; set; } = new HttpHeadersCollection();
public override Stream Body { get; set; } = new MemoryStream();
public override HttpCookies Cookies { get; }
}
}
And the test is (ExportFuncUnitTests2.cs):
using Xunit;
using ExportFuncApp;
using System.Threading.Tasks;
using System.IO;
using System.Text;
using Moq;
using Microsoft.Azure.Functions.Worker;
using System;
using System.Net;
using Microsoft.Extensions.Logging.Abstractions;
namespace ExportFuncAppUnitTestsXunit
{
public class ExportFuncUnitTests2
{
[Fact]
public async Task Http_trigger_should_return_known_string()
{
// Arrange
var body = new MemoryStream(Encoding.ASCII.GetBytes("{ \"test\": true }"));
var context = new Mock<FunctionContext>();
var request = new FakeHttpRequestData(
context.Object,
new Uri("https://stackoverflow.com"),
body);
// Act
var function = new ExportFunc(new NullLogger<ExportFunc>());
var result = await function.Run(request, context);
result.HttpResponse.Body.Position = 0;
// Assert
var reader = new StreamReader(result.HttpResponse.Body);
var responseBody = await reader.ReadToEndAsync();
Assert.NotNull(result);
Assert.Equal(HttpStatusCode.OK, result.HttpResponse.StatusCode);
Assert.Equal("Hello test", responseBody);
}
}
}
The ExportFuncUnitTests2.cs has the following compilation errors:
CS1729 'ExportFunc' does not contain a constructor that takes 1 arguments
for:
var function = new ExportFunc(new NullLogger<ExportFunc>());
and
CS1503 Argument 2: cannot convert from 'Moq.Mock<Microsoft.Azure.Functions.Worker.FunctionContext>' to 'Microsoft.Azure.Functions.Worker.FunctionContext'
for:
var result = await function.Run(request, context);
I the article Guide for running C# Azure Functions in an isolated process was somewhat useful but no help in terms of unit testing. Maybe I am missing the point. Since there is no examples/documentation on how to do unit testing for AF 3.0 with .NET 5.0 from Microsoft then I should not be trying to do that?
Any pointers will be greatly appreciated. Thanks!
Your Run() function in ExportFunc is not awaitable. Therefore, you should replace the line:
var result = await function.Run(request, context)
with:
var result = function.Run(request, context).
If you want to test an awaitable function (i.e., async Run()), then you'd call it from your Unit Test with await.
I am using the default configuration of uCommerce and see that uCommerce nice URLs are not language aware: http://sitename/catalogname/productname/c-XX/p-YY.
What should I do to have language in those URLs like this: http://sitename/en/catalogname/productname/c-XX/p-YY ?
Here is the configuration:
<linkManager defaultProvider="sitecore">
<providers>
<clear />
<add name="sitecore" type="Sitecore.Links.LinkProvider, Sitecore.Kernel" addAspxExtension="false" alwaysIncludeServerUrl="false" encodeNames="true" languageEmbedding="always" languageLocation="filePath" lowercaseUrls="true" shortenUrls="true" useDisplayName="true" />
</providers>
</linkManager>
Here is how I use it:
public WebshopProduct Map(UCommerceProduct uProduct)
{
ProductCatalog catalog = CatalogLibrary.GetCatalog(25);
IUrlService urlService = ObjectFactory.Instance.Resolve<IUrlService>();
...
var url = urlService.GetUrl(catalog, uProduct) // this returns "/catalogname/productname/c-XX/p-YY"
//And I would like to have "/en/catalogname/productname/c-XX/p-YY"
}
Adding language to URL depends on how you are rendered links. If you don't pass specific parameters than Sitecore (and uCommerce as part of Sitecore) uses LinkManager configuration sitecore>linkManager>providers: languageEmbedding adn languageLocation attributes. You should have languageEmbedding="always" and languageLocation="filePath"
P.S.
But, be aware if you using their demo or something based on their demo(e.g. from certification courses): they uses regular ASP.Net MVC(not Sitecore MVC). And links are not rendered via LinkManager and you should put language to URL by youself. Register routed with language code that is embedded to them.
Here is what I have come up with:
public static class TemplateIDs
{
// sitecore/ucommerce item's template id
public static ID UCommerce => new ID("{AABC1CFA-9CDB-4AE5-8257-799D84A8EE23}");
}
public static class ItemExtensions
{
public static bool IsUCommerceItem(this Item item)
{
var items = item.Axes.GetAncestors();
return items.Any(x => x.TemplateID.Equals(TemplateIDs.UCommerce));
}
}
public static string GetItemUrlByLanguage(Sitecore.Globalization.Language language)
{
if (Context.Item.IsUCommerceItem() && SiteContext.Current.CatalogContext.CurrentProduct != null && SiteContext.Current.CatalogContext.CurrentProduct.Guid == Context.Item.ID.Guid)
{
ProductCatalog catalog = CatalogLibrary.GetCatalog(25);
IUrlService urlService = ObjectFactory.Instance.Resolve<IUrlService>();
var url = "/" + language.CultureInfo.TwoLetterISOLanguageName + urlService.GetUrl(catalog, SiteContext.Current.CatalogContext.CurrentProduct);
return url;
}
else
{
//Normal URL creation
using (new LanguageSwitcher(language))
{
var options = new UrlOptions
{
AlwaysIncludeServerUrl = true,
LanguageEmbedding = LanguageEmbedding.Always,
LowercaseUrls = true
};
var url = LinkManager.GetItemUrl(Context.Item, options);
url = StringUtil.EnsurePostfix('/', url).ToLower();
return url;
}
}
}
I have two publishing target - one is stage and one is production.The publishing on Production should adhere to the publishing restrictions, but publishing on Stage should not look at or discard the valid to and valid from dates and publish under any circumstance.
I have written a publishing pipeline (PipelinePublishProvider). I am not sure how could I manage to overwrite the field values temporarily so it publishes on to stage every-time.
public class StagePublishOverride : PipelinePublishProvider
{
public override PublishHelper CreatePublishHelper(PublishOptions options)
{
Assert.ArgumentNotNull(options, "options");
if (options.TargetDatabase.Name.ToLower() == "stage")
{
Item itemToBePublished = new Item(options.RootItem.ID, options.RootItem.InnerData, new Database("web"));
itemToBePublished.Editing.BeginEdit();
itemToBePublished.Publishing.ValidTo = DateTime.MaxValue;
itemToBePublished.Publishing.ValidFrom = DateTime.MinValue;
itemToBePublished.Editing.EndEdit();
options.RootItem = itemToBePublished;
}
if (options is ExtendedPublishOptions)
return new ExtendedPublishHelper(options as ExtendedPublishOptions);
return new PublishHelper(options);
}
}
public class ExtendedPublishHelper : PublishHelper
{
private readonly ExtendedPublishOptions _options;
public ExtendedPublishHelper(ExtendedPublishOptions options)
: base(options)
{
_options = options;
}
public override Item GetVersionToPublish(Item sourceItem)
{
Assert.ArgumentNotNull(sourceItem, "sourceItem");
if (Options is ExtendedPublishOptions)
{
return sourceItem.Publishing.GetValidVersion(Options.PublishDate, _options.RequireApproval);
}
return sourceItem.Publishing.GetValidVersion(Options.PublishDate, true);
}
}
public class ExtendedPublishOptions : PublishOptions
{
public ExtendedPublishOptions(Database sourceDatabase, Database targetDatabase, PublishMode mode, Language language, DateTime publishDate, bool requireApproval)
: base(sourceDatabase, targetDatabase, mode, language, publishDate)
{
RequireApproval = requireApproval;
}
public bool RequireApproval { get; set; }
}
}
I believe you'll be better off adding a processor to the publishItem pipeline. Here is some UNTESTED code, which I think will serve your purpose:
public class PublishOverride : PublishItemProcessor
{
public override void Process(PublishItemContext context)
{
Assert.ArgumentNotNull((object)context, "context");
if (context.Action != PublishAction.None)
return;
Item sourceItem = this.GetSourceItem(context);
if (sourceItem == null)
return;
var stagingDB = Factory.GetDatabase("Stage");
if (stagingDB != null && !sourceItem.Publishing.NeverPublish && context.PublishContext.PublishOptions.TargetDatabase == stagingDB)
{
context.Action = PublishAction.PublishVersion;
context.VersionToPublish = sourceItem;
}
}
private Item GetSourceItem(PublishItemContext context)
{
Assert.ArgumentNotNull((object)context, "context");
return context.PublishHelper.GetSourceItem(context.ItemId);
}
}
Make sure you patch it in before the DetermineAction processor in the default config. So your config patch would look like this:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:x="http://www.sitecore.net/xmlconfig/">
<sitecore>
<pipelines>
<publishItem>
<processor patch:before="*[#type='Sitecore.Publishing.Pipelines.PublishItem.DetermineAction, Sitecore.Kernel']"
type="YourNamespace.PublishOverride, YourAssembly" />
</publishItem>
</pipelines>
</sitecore>
</configuration>
I have a MVC controller that loads a resource file and uses Server.MapPath to get the path to the file. I want to mock out the Server property in the controller object using Microsoft Fakes framework (I know how to do this using other frameworks).
Here's the code:
[HttpGet]
public ActionResult GeneratePdf(string reportId)
{
var template = LoadTemplate(reportId);
var document = pdfWriter.Write(GetReportModel(reportId), template);
return File(document, MediaTypeNames.Application.Pdf);
}
private byte[] LoadTemplate(string reportId)
{
var templatePath = Server.MapPath(string.Format("~/ReportTemplates/{0}.docx", reportId));
using(var templateContent = System.IO.File.OpenText(templatePath))
{
return Encoding.Default.GetBytes(templateContent.ReadToEnd());
}
}
The part I'm trying to mock out is the "Server.MapPath" method.
As of Visual Studio 2012 Update 1, you can detour the Controller.Server property using Stubs.
With the following .Fakes file in your test project:
<Fakes xmlns="http://schemas.microsoft.com/fakes/2011/" Diagnostic="true">
<Assembly Name="System.Web" Version="4.0.0.0"/>
<StubGeneration>
<Clear/>
<Add FullName="System.Web.HttpContextBase!"/>
<Add FullName="System.Web.HttpServerUtilityBase!"/>
</StubGeneration>
<ShimGeneration>
<Clear/>
</ShimGeneration>
</Fakes>
You can write the a unit test like this:
[TestMethod]
public void TestMethod1()
{
var target = new TestController();
var serverStub = new StubHttpServerUtilityBase();
serverStub.MapPathString = (path) => path.Replace("~", string.Empty).Replace("/", #"\");
var contextStub = new StubHttpContextBase();
contextStub.ServerGet = () => serverStub;
target.ControllerContext = new ControllerContext();
target.ControllerContext.HttpContext = contextStub;
var result = (FilePathResult) target.Index();
Assert.AreEqual(#"\Content\Test.txt", result.FileName);
}
With the upcoming Update 2, you will also be able to detour Controller.Server property directly using Shims. Here is the additional .Fakes file you will need with this approach.
<Fakes xmlns="http://schemas.microsoft.com/fakes/2011/" Diagnostic="true">
<Assembly Name="System.Web.Mvc" Version="4.0.0.0"/>
<StubGeneration>
<Clear/>
</StubGeneration>
<ShimGeneration>
<Clear/>
<Add FullName="System.Web.Mvc.Controller!"/>
</ShimGeneration>
</Fakes>
And here is the test:
[TestMethod]
public void TestMethod2()
{
using (ShimsContext.Create())
{
var target = new TestController();
var serverStub = new StubHttpServerUtilityBase();
serverStub.MapPathString = (path) => path.Replace("~", string.Empty).Replace("/", #"\");
var controllerShim = new ShimController(target);
controllerShim.ServerGet = () => serverStub;
var result = (FilePathResult)target.Index();
Assert.AreEqual(#"\Content\Test.txt", result.FileName);
}
}
Please note that this approach does not work in the current version (Update 1), due to limitations in Fakes runtime related to assemblies, like System.Web.Mvc that allow partially trusted callers. If you try to run the second test today, you will get a VerificationException.
I am using jQuery ajax to calling a web service method but is is not doing and generating error..
The code is here for jQuery ajax in asp page
var indexNo = 13; //pass the value
$(document).ready(function() {
$("#a1").click(function() {
$.ajax({
type: "POST",
url: "myWebService.asmx/GetNewDownline",
data: "{'indexNo':user_id}",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(msg) {
$("#divResult").text(msg.d);
}
});
});
});
and this is the is web service method
using System;
using System.Collections;
using System.Linq;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml.Linq;
using System.Data;
using System.Web.Script.Serialization;
using TC.MLM.DAL;
using TC.MLM.BLL.AS;
/// <summary>
/// Summary description for myWebService
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
[System.Web.Script.Services.ScriptService]
public class myWebService : System.Web.Services.WebService
{
public myWebService()
{
//Uncomment the following line if using designed components
//InitializeComponent();
}
[WebMethod]
public string HelloWorld()
{
return "Hello World";
}
[WebMethod]
public string GetNewDownline(string indexNo)
{
IndexDetails indexDtls = new IndexDetails();
indexDtls.IndexNo = "13";
DataSet ds = new DataSet();
ds = TC.MLM.BLL.AS.Index.getIndexDownLineByIndex(indexDtls);
indexNoDownline[] newDownline = new indexNoDownline[ds.Tables[0].Rows.Count];
for (int count = 0; count <= ds.Tables[0].Rows.Count - 1; count++)
{
newDownline[count] = new indexNoDownline();
newDownline[count].adjustedid = ds.Tables[0].Rows[count]["AdjustedID"].ToString();
newDownline[count].name = ds.Tables[0].Rows[count]["name"].ToString();
newDownline[count].structPostion = ds.Tables[0].Rows[count]["Struct_Position"].ToString();
newDownline[count].indexNo = ds.Tables[0].Rows[count]["IndexNo"].ToString();
newDownline[count].promoterId = ds.Tables[0].Rows[count]["PromotorID"].ToString();
newDownline[count].formNo = ds.Tables[0].Rows[count]["FormNo"].ToString();
}
JavaScriptSerializer serializer = new JavaScriptSerializer();
JavaScriptSerializer js = new JavaScriptSerializer();
string resultedDownLine = js.Serialize(newDownline);
return resultedDownLine;
}
public class indexNoDownline
{
public string adjustedid;
public string name;
public string indexNo;
public string structPostion;
public string promoterId;
public string formNo;
}
}
Please help me something.
There is a problem with your input JSON data. Instead of manual serialization you should try to use the builtin JSON class. Ex.
$.ajax({
...
data: JSON.stringify({ indexNo: user_id }),
...
});
This should fix your problem.
You should change the art how you implement serialization in the server. Just add ScriptMethod attribute with ResponseFormat = ResponseFormat.Json. Using of HTTP GET is also possible with respect of additional (optional) attribute UseHttpGet = true
[WebMethod]
[ScriptMethod (UseHttpGet = true, ResponseFormat = ResponseFormat.Json)]
public string GetNewDownline(string indexNo)
The database access can be also improved if you will use SqlConnection, SqlCommand and SqlDataReader classes or another versions of DbDataReader depends of you database source (OleDbDataReader, OracleDataReader, OdbcDataReader, DataTableReader). If you prefer work with strong typed data, then SqlDataAdapter generated by Visual Studio will be better version of DataSet. To do this just add a new Item to your profject, choose "Data" / "DataSet" and then add a Query or TableAdapter.
If you decide to use HTTP GET you should don't make corresponding modification of your web.config file.
<configuration>
<!-- ... -->
<system.web>
<webServices>
<protocols>
<add name="HttpGet"/>
</protocols>
</webServices>
<!-- ... -->
</system.web>
</configuration>
About usage of JSON.stringify I have the same opinion like "egyedg".
I recommend you to look following links:
Can I return JSON from an .asmx Web Service if the ContentType is not JSON?
How do I build a JSON object to send to an AJAX WebService?
JQuery ajax call to httpget webmethod (c#) not working